////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2009 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
//
////////////////////////////////////////////////////////////////////////////////
package mx.charts.chartClasses
{
    
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.utils.Dictionary;
import mx.core.mx_internal;
import mx.core.IFlexDisplayObject;
import mx.core.IUIComponent;

use namespace mx_internal;

[DefaultProperty("dataChildren")]

/**
 * This class lets you use add graphical elements such as lines, ellipses, and other shapes
 * by using a graphics API. The values that you pass to the graphics API are in data
 * coordinates rather than screen coordinates. You can also add any DisplaObject to the canvas,
 * in the same way that you add children to containers.
 * 
 * <p>The drawing region for the canvas is determined by the <code>verticalAxis</code>
 * and <code>horizontalAxis</code>, if they are specified. Otherwise,
 * the canvas uses the default axes of the chart to compute the drawing region.</p>
 * 
 * <p>The data coordinates passed as parameters to the drawing APIs can be 
 * actual values of the data coordinate or an object of type <code>CartesianCanvasValue</code>,
 * which can hold a data coordinate value and an offset, in pixels.</p>
 * 
 * @mxml
 *  
 *  <p>The <code>&lt;mx:CartesianDataCanvas&gt;</code> tag inherits all the
 *  properties of its parent classes and adds the following properties:</p>
 *  
 *  <pre>
 *  &lt;mx:CartesianDataCanvas
 *    <strong>Properties</strong>
 *    dataChildren="<i>No default</i>"
 *    horizontalAxis="<i>No default</i>"
 *    includeInRanges="<i>false</i>"
 *    verticalAxis="<i>No default</i>"
 *  /&gt;
 *  </pre>
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class CartesianDataCanvas extends ChartElement implements IDataCanvas
{
    include "../../core/Version.as";

    //----------------------------------------------
    //
    // Constructor
    //
    //----------------------------------------------
    
    /**
     * Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function CartesianDataCanvas()
    {
        super();
        
        dataTransform = new CartesianTransform();
    }
    
    //-----------------------------------------------
    //
    // Variables
    //
    //-----------------------------------------------
          
    private var _data:*;
    private var _offset:Number;
    private var _xMap:Dictionary;
    private var _yMap:Dictionary;
    private var _hDataDesc:DataDescription = new DataDescription();
    private var _vDataDesc:DataDescription = new DataDescription();
    
    private var _dataCache:CartesianDataCache;
    private var _dataCacheDirty:Boolean = true;
    private var _mappingDirty:Boolean = true;
    private var _filterDirty:Boolean = true;
    private var _transformDirty:Boolean = true;
    private var _oldUW:Number;
    private var _oldUH:Number;
    private var borderWidth:Number = 0;
    private var _bAxesDirty:Boolean = false;
    private var _childMap:Dictionary = new Dictionary(true);
    
    //----------------------------------------------
    //
    // Overridden Properties
    //
    //----------------------------------------------
    
    //----------------------------------
    //  dataTransform
    //----------------------------------

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function set dataTransform(value:DataTransform):void
    {
        if (value)
        {
            super.dataTransform = value;
        }
        else
        {
            for (var p:String in dataTransform.axes)
            {
                dataTransform.getAxis(p).unregisterDataTransform(dataTransform);
            }
        }
    }  
    
    
    //----------------------------------------------
    //
    // Properties
    //
    //----------------------------------------------
    
    //-----------------------------------------
    //  dataChildren
    //-----------------------------------------
    
    /**
     * @private
     * Storage for dataChildren property
     */
    private var _dataChildren:Array /* of DisplayObject */ = [];
    [Inspectable(category="General")]
    
    /**
     * An array of child objects.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get dataChildren():Array /* of DisplayObject */
    {
        return _dataChildren;
    }
    
    /**
     * @private
     */     
    public function set dataChildren(value:Array /* of DisplayObject */):void
    {
        for (var p:* in _childMap)
        {
            removeChild(_childMap[p].child);
        }
        _childMap = new Dictionary(true);
        _dataChildren = value;
        var n:int = value.length;
        for (var i:int = 0; i < n; i++)
        {
            var dc:CartesianDataChild;
            if (value[i] is CartesianDataChild)               
                dc = value[i];
            else
                dc = new CartesianDataChild(value[i]);
                
            _childMap[dc.child] = dc;
            dc.addEventListener("change",dataChildChangeHandler,false,0,true);
            super.addChild(dc.child);           
        }
        invalidateOpCodes();
    }    
    
    //----------------------------------
    //  horizontalAxis
    //----------------------------------

    /**
     *  @private
     *  Storage for the horizontalAxis property.
     */
    private var _horizontalAxis:IAxis;
    
    [Inspectable(category="Data")]

    /**
     *  Defines the labels, tick marks, and data position
     *  for items on the x-axis.
     *  Use either the LinearAxis class or the CategoryAxis class
     *  to set the properties of the horizontal axis as a child tag in MXML
     *  or create a LinearAxis or CategoryAxis object in ActionScript.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get horizontalAxis():IAxis
    {
        return _horizontalAxis;
    }
    
    /**
     * @private
     */
    public function set horizontalAxis(value:IAxis):void
    {
        _horizontalAxis = value;
        _bAxesDirty = true;
        
        invalidateData();
        invalidateProperties();
    }
    
    //----------------------------------
    //  includeInRanges
    //----------------------------------
    
    /**
     * @private
     * Storage for includeInRanges property
     */
    private var _includeInRanges:Boolean = false;
    [Inspectable(category="General")]
    
    /**
     * If <code>true</code>, the computed range of the chart is affected by this
     * canvas.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    
    public function get includeInRanges():Boolean
    {
        return _includeInRanges;
    }

    /**
     * @private
     */
    public function set includeInRanges(value:Boolean):void
    {
        if (_includeInRanges == value)
            return;
        _includeInRanges = value;
        dataChanged();
    }
    
    //--------------------------------------------
    // opCodes
    //--------------------------------------------
    /**
     * @private
     * Storage for opCodes property
     */
    private var _opCodes:Array /* of CartesianOpCode */ = [];
        
    [ArrayElementType("CartesianOpCode")]
    
    /**
     * @private
     */
     
    private function get opCodes():Array /* of CartesianOpCode */
    {
        return _opCodes;
    }
    
    /**
     * @private
     */
    private function set opCodes(value:Array /* of CartesianOpCode */):void
    {       
        _opCodes = value;
        invalidateOpCodes();
    }
    
    
    //----------------------------------
    //  verticalAxis
    //----------------------------------

    /**
     *  @private
     *  Storage for the verticalAxis property.
     */
    private var _verticalAxis:IAxis;

    [Inspectable(category="Data")]

    /**
     *  Defines the labels, tick marks, and data position
     *  for items on the y-axis.
     *  Use either the LinearAxis class or the CategoryAxis class
     *  to set the properties of the vertical axis as a child tag in MXML
     *  or create a LinearAxis or CategoryAxis object in ActionScript.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get verticalAxis():IAxis
    {
        return _verticalAxis;
    }
    
    /**
     *  @private
     */
    public function set verticalAxis(value:IAxis):void
    {
        _verticalAxis = value;
        _bAxesDirty = true;
        invalidateData();
        invalidateProperties();
    }
    
    
    //----------------------------------------------
    //
    // Overridden Methods
    //
    //----------------------------------------------
    
    /**
     * @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function addChild(child:DisplayObject):DisplayObject
    {
        var dc:CartesianDataChild = new CartesianDataChild(child);
        _childMap[child] = dc;
        _dataChildren.push(dc);     
        dc.addEventListener("change",dataChildChangeHandler,false,0,true);
        invalidateOpCodes();
        return super.addChild(child);
    }
    
    /**
     * @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function addChildAt(child:DisplayObject,index:int):DisplayObject
    {
        var dc:CartesianDataChild = new CartesianDataChild(child);
        _childMap[child] = dc;
        _dataChildren.push(dc);         
        dc.addEventListener("change",dataChildChangeHandler,false,0,true);
        invalidateOpCodes();
        return super.addChildAt(child,index);
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override protected function commitProperties():void
    {
        super.commitProperties();
        
        if (_bAxesDirty)
        {
            if (dataTransform)
            {
                if (_horizontalAxis)
                {
                    _horizontalAxis.chartDataProvider = dataProvider;
                    CartesianTransform(dataTransform).setAxis(
                        CartesianTransform.HORIZONTAL_AXIS,_horizontalAxis);
                }
                
                if (_verticalAxis)
                {
                    _verticalAxis.chartDataProvider = dataProvider;
                    CartesianTransform(dataTransform).setAxis(
                        CartesianTransform.VERTICAL_AXIS, _verticalAxis);
                }
            }
            _bAxesDirty = false; 
        }
        
        var c:CartesianChart = CartesianChart(chart);
        if (c)
        {
            if (!_horizontalAxis)
            {
                if (dataTransform.axes[CartesianTransform.HORIZONTAL_AXIS] != c.horizontalAxis)
                        CartesianTransform(dataTransform).setAxis(
                            CartesianTransform.HORIZONTAL_AXIS,c.horizontalAxis);
            }
                            
            if (!_verticalAxis)
            {
                if (dataTransform.axes[CartesianTransform.VERTICAL_AXIS] != c.verticalAxis)
                        CartesianTransform(dataTransform).setAxis(
                            CartesianTransform.VERTICAL_AXIS, c.verticalAxis);
            }
        }
        dataTransform.elements = [this];
        invalidateOpCodes();
    }
    
        
    /**
     * @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function removeChild(child:DisplayObject):DisplayObject
    {       
        super.removeChild(child);
        if (child in _childMap)
            delete _childMap[child];
        _dataChildren.splice(_dataChildren.indexOf(child),1);
        return child;
    }
    
    /**
     * @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */ 
    override public function removeChildAt(index:int):DisplayObject
    {
        var child:DisplayObject = super.removeChildAt(index);
        if (child in _childMap)
            delete _childMap[child];
        _dataChildren.splice(index,1);
        return child;
    }
    
    /**
     * @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
        
    override protected function updateDisplayList(unscaledWidth:Number,
                                                  unscaledHeight:Number):void
    {
        
        super.updateDisplayList(unscaledWidth, unscaledHeight);

        
        validateData();
        
        var updated:Boolean = validateTransform();
        
        if (updated)
        {
            var g:Graphics = graphics;
            
            g.clear();
            var n:int = _opCodes.length;
            for (var i:int = 0; i < n; i++)
            { 
                _opCodes[i].render(this,_dataCache);
            }
            positionChildren();
        }
    }
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function invalidateDisplayList():void
    {
        _dataCacheDirty = true;
        _mappingDirty = true;
        _filterDirty = true;
        _transformDirty = true;
        super.invalidateDisplayList();
    } 
    
    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function dataToLocal(... dataValues):Point
    {
        var data:Object = {};
        var da:Array /* of Object */ = [ data ];
        var n:int = dataValues.length;
        
        if (n > 0)
        {
            data["d0"] = dataValues[0];
            dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).
                mapCache(da, "d0", "v0");
        }
        
        if (n > 1)
        {
            data["d1"] = dataValues[1];
            dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).
                mapCache(da, "d1", "v1");           
        }

        dataTransform.transformCache(da,"v0","s0","v1","s1");
        
        return new Point(data.s0 + this.x,
                         data.s1 + this.y);
    }

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function localToData(v:Point):Array /* of Object */
    {
        var values:Array /* of Object */ = dataTransform.invertTransform(
                                            v.x - this.x,
                                            v.y - this.y);
        return values;
    }
    
    /**
     * @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function describeData(dimension:String,
                                          requiredFields:uint):Array /* of DataDescription */
    {
        updateMapping();
        var result:Array /* of DataDescription */ = [];

        if (_includeInRanges)
        {
            if (dimension == CartesianTransform.VERTICAL_AXIS)
            {
                if (_dataCache.xCache.length > 0)
                    result.push(_vDataDesc);
            }
            else if (dimension == CartesianTransform.HORIZONTAL_AXIS)
            {
                if (_dataCache.yCache.length > 0)
                    result.push(_hDataDesc);
            }
        }

        return result;  
    }
        
    //----------------------------------------------
    //
    // Methods
    //
    //----------------------------------------------

    mx_internal function dataChildChangeHandler(event:Event):void
    {
        dataChildren = dataChildren;
    }

    /**
     * Adds the specified display object as a child to the current canvas.
     * 
     * @param child     The display object that is to be added as a child to current canvas.
     * @param left      Left x-coordinate of the <code>child</code> in data coordinates.
     * @param top       Top y-coordinate of the <code>child</code> in data coordinates.
     * @param right     Right x-coordinate of the <code>child</code> in data coordinates.
     * @param bottom    Bottom y-coordinate of the <code>child</code> in data coordinates.
     * @param hCenter   Middle x-coordinate of the <code>child</code> in data coordinates.
     * @param vCenter   Middle y-coordinate of the <code>child</code> in data coordinates.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function addDataChild(child:DisplayObject,left:* = undefined, top:* = undefined, right:* = undefined, 
                                 bottom:* = undefined , hCenter:* = undefined, vCenter:* = undefined):void
    {
        var dc:CartesianDataChild = new CartesianDataChild(child,left,top,right,bottom);
        dc.addEventListener("change",dataChildChangeHandler,false,0,true);
        addChild(child);
        updateDataChild(child,left,top,right,bottom,hCenter,vCenter);
        invalidateOpCodes();
    }    
    
    /**
     * Removes all data children (DisplayObject instances) of the canvas.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function removeAllChildren():void
    {
        var n:int = _dataChildren.length;
        for (var i:int = n - 1; i >= 0; i--)
        {
            removeChildAt(i);
        }
    }
    
    /**
     * Updates the position of any child to current canvas.
     * 
     * @param child     The display object that is to be updated.
     * @param left      Left x coordinate of the child, in data coordinates.
     * @param top       Top y coordinate of the child, in data coordinates.
     * @param right     Right x coordinate of the child, in data coordinates.
     * @param bottom    Bottom y coordinate of the child, in data coordinates.
     * @param hCenter   Middle x coordinate of the child, in data coordinates.
     * @param vCenter   Middle y coordinate of the child, in data coordinates.
     * 
     * <p>For example:
     * <pre>
     *      var lbl:Label = new Label();
     *      lbl.text = "Last Month";
     *      canvas.addChild(lbl);
     *      canvas.updateDataChild(lbl,"Feb",200);
     * </pre>
     * </p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function updateDataChild(child:DisplayObject,left:* = undefined, top:* = undefined, right:* = undefined,
                                    bottom:* = undefined, hCenter:* = undefined, vCenter:* = undefined):void
    {
        var dc:CartesianDataChild = _childMap[child];
        dc.left = left;
        dc.top = top;
        dc.right = right;
        dc.bottom = bottom;
        dc.horizontalCenter = hCenter;
        dc.verticalCenter = vCenter;
        invalidateOpCodes();
    }

    /**
     * @copy flash.display.Graphics#clear()
     * @see flash.display.Graphics
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function clear():void
    {
        _opCodes = [];
        invalidateOpCodes();
    }

    /**
     * <i>Note: With the exception of the <code>beginGradientFill()</code> and <code>beginShaderFill()</code> methods, the following information
     * from the <code>flash.display.Graphics</code> class's <code>beginFill()</code> method applies to this method:</i>
     *  
     * @copy flash.display.Graphics#beginFill()
     * @see flash.display.Graphics
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function beginFill(color:uint , alpha:Number = 1):void
    {
        pushOp(CartesianOpCode.BEGIN_FILL, { color: color, alpha: alpha} );
    }
    
    /**
     * Fills a drawing area with a bitmap image. The coordinates that you pass to this method are relative to 
     * the canvas's horizontal axis and vertical axis.
     * 
     * <p>The usage and parameters of this method are identical to the <code>beginBitmapFill()</code> method of the 
     * flash.display.Graphics class.</p>
     * 
     * @param bitmap A transparent or opaque bitmap image that contains the bits to be displayed. 
     *
     * @param x The x coordinate of the fill.
     *
     * @param y The y coordinate of the fill.
     *
     * @param matrix A matrix object (of the flash.geom.Matrix class), 
     * which you can use to define transformations on the bitmap.
     *
     * @param repeat If <code>true</code>, the bitmap image repeats in a tiled pattern. 
     * If <code>false</code>, the bitmap image does not repeat, and the edges of 
     * the bitmap are used for any fill area that extends beyond the bitmap.
     *
     * @param smooth If <code>false</code>, upscaled bitmap images are rendered 
     * by using a nearest-neighbor algorithm and look pixelated. 
     * If <code>true</code>, upscaled bitmap images are rendered by using a bilinear algorithm. 
     * Rendering by using the nearest neighbor algorithm is faster. 
     *
     * @see flash.display.Graphics#beginBitmapFill()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function beginBitmapFill(bitmap:BitmapData, x:* = undefined,
                                    y:* = undefined, matrix:Matrix = null,
                                    repeat:Boolean = true, smooth:Boolean = true):void
    {
        pushOp(CartesianOpCode.BEGIN_BITMAP_FILL, { bitmap:bitmap, x:x, y:y, repeat:repeat, smooth:smooth, matrix:matrix });
    }
    
    /**
     * Draws a curve using the current line style from the current drawing position to (anchorX, anchorY) and using the 
     * control point that (controlX, controlY) specifies. The coordinates that you pass to this method are in terms of 
     * chart data rather than screen coordinates.
     * 
     * <p>The usage and parameters of this method are identical to the <code>curveTo()</code> method of the 
     * flash.display.Graphics class.</p>
     *
     * @param controlX The x coordinate of the control point. 
     *
     * @param controlY The y coordinate of the control point.
     *
     * @param anchorX The x coordinate of the anchor point. 
     *
     * @param anchorY The y coordinate of the anchor point.  
     *
     * @see flash.display.Graphics#curveTo()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function curveTo(controlX:*, controlY:*, anchorX:*, anchorY:*):void
    {
        pushOp(CartesianOpCode.CURVE_TO, { controlX: controlX, controlY:controlY, anchorX:anchorX, anchorY:anchorY, borderWidth: borderWidth } );
    }
    
    /**
     * Draws a circle. 
     * Set the line style, fill, or both before you call the <code>drawCircle()</code> method, 
     * by calling the <code>linestyle()</code>, <code>lineGradientStyle()</code>, 
     * <code>beginFill()</code>, <code>beginGradientFill()</code>, or <code>beginBitmapFill()</code> method. 
     * The coordinates that you pass to this method are in terms of chart data rather than screen coordinates.
     * 
     * @param x The x location of the center of the circle (in pixels). 
     * 
     * @param y The y location of the center of the circle (in pixels). 
     * 
     * @param radius The radius of the circle (in pixels). 
     *
     * @see flash.display.Graphics
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function drawCircle(x:*, y:*, radius:Number):void
    {
        pushOp(CartesianOpCode.DRAW_CIRCLE, { x: x, y: y, radius: radius, borderWidth: borderWidth });
    }
    
    /**
     * Draws an ellipse. 
     * Set the line style, fill, or both before you call the <code>drawEllipse()</code> method, 
     * by calling the <code>linestyle()</code>, <code>lineGradientStyle()</code>, 
     * <code>beginFill()</code>, <code>beginGradientFill()</code>, or <code>beginBitmapFill()</code> method. 
     * The coordinates that you pass to this method are in terms of chart data rather than screen coordinates.
     * 
     * @param left The x location of the top-left corner of the bounding-box of the ellipse.
     * 
     * @param top The y location of the top-left corner of the bounding-box of the ellipse.
     * 
     * @param right The x location of the bottom-right corner of the bounding-box of the ellipse.
     * 
     * @param bottom The y location of the bottom-right corner of the bounding-box of the ellipse.
     * 
     * @see flash.display.Graphics#drawEllipse()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function drawEllipse(left:*, top:*, right:*, bottom:*):void
    {
        pushOp(CartesianOpCode.DRAW_ELLIPSE, { left: left, top: top, right: right, bottom: bottom, borderWidth: borderWidth });
    }
    
    /**
     * Draws a rectangle. 
     * Set the line style, fill, or both before you call the <code>drawRect()</code> method, 
     * by calling the <code>linestyle()</code>, <code>lineGradientStyle()</code>, 
     * <code>beginFill()</code>, <code>beginGradientFill()</code>, or <code>beginBitmapFill()</code> method. 
     * The coordinates that you pass to this method are in terms of chart data rather than screen coordinates.
     * 
     * @param left The x location of the top-left corner of the rectangle.
     * 
     * @param top The y location of the top-left corner of the rectangle.
     * 
     * @param right The x location of the bottom-right corner of the rectangle.
     * 
     * @param bottom The y location of the bottom-right corner of the rectangle.
     *
     * @see flash.display.Graphics#drawRect()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function drawRect(left:*, top:*, right:*, bottom:*):void
    {
        pushOp(CartesianOpCode.DRAW_RECT, { left: left, top: top, right: right, bottom: bottom, borderWidth: borderWidth });
    }
    
    /**
     * Draws a rounded rectangle. 
     * Set the line style, fill, or both before you call the <code>drawRoundRect()</code> method, 
     * by calling the <code>linestyle()</code>, <code>lineGradientStyle()</code>, 
     * <code>beginFill()</code>, <code>beginGradientFill()</code>, or <code>beginBitmapFill()</code> method. 
     * The coordinates that you pass to this method are in terms of chart data rather than screen coordinates.
     * 
     * @param left The x location of the top-left corner of the rectangle.
     * 
     * @param top The y location of the top-left corner of the rectangle.
     * 
     * @param right The x location of the bottom-right corner of the rectangle.
     * 
     * @param bottom The y location of the bottom-right corner of the rectangle.
     * 
     * @param cornerRadius The radius of the corners, in pixels.
     * 
     * @see flash.display.Graphics#drawRoundRect()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function drawRoundedRect(left:*, top:*, right:*, bottom:*, cornerRadius:Number):void
    {
        pushOp(CartesianOpCode.DRAW_ROUNDRECT, { left: left, top: top, right: right, bottom: bottom, 
                                    borderWidth: borderWidth,
                                    cornerRadius: cornerRadius });
    }
    
    /** 
     * <i>Note: With the exception of the <code>beginGradientFill()</code> method, the following information
     * from the <code>flash.display.Graphics</code> class's <code>endFill()</code> applies to this method:</i>
     * 
     * @copy flash.display.Graphics#endFill()
     * @see flash.display.Graphics
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function endFill():void
    {
        pushOp(CartesianOpCode.END_FILL);
    }
    
    /**
     * Specifies a line style that Flash uses for subsequent calls to other Graphics methods (such as <code>lineTo()</code> 
     * or <code>drawCircle()</code>) for the object.
     * 
     * <p>The usage and parameters of this method are identical to the <code>lineStyle()</code> method of the 
     * flash.display.Graphics class.</p>
     *
     * @param thickness An integer that indicates the thickness of the line in points; valid values are 0-255. 
     * If a number is not specified, or if the parameter is undefined, a line is not drawn. 
     * If a value of less than 0 is passed, the default is 0. 
     * The value 0 indicates hairline thickness; the maximum thickness is 255. 
     * If a value greater than 255 is passed, the default is 255. 
     * 
     * @param color A hexadecimal color value of the line; for example, red is 0xFF0000, blue is 0x0000FF, and so on. 
     * If a value is not indicated, the default is 0x000000 (black).
     * 
     * @param alpha A number that indicates the alpha value of the color of the line; valid values are 0 to 1. 
     * If a value is not indicated, the default is 1 (solid). 
     * If the value is less than 0, the default is 0. 
     * If the value is greater than 1, the default is 1.
     * 
     * @param pixelHinting A Boolean value that specifies whether to hint strokes to full pixels. 
     * This affects both the position of anchors of a curve and the line stroke size itself. 
     * With <code>pixelHinting</code> set to true, line widths are adjusted to full pixel widths. 
     * With <code>pixelHinting</code> set to false, disjoints can appear for curves and straight lines.
     * 
     * @param scaleMode A value from the flash.display.LineScaleMode class that specifies which scale mode to use:
     * 
     * <ul>
     *   <li><code>LineScaleMode.NORMAL</code>: Always scale the line thickness when the object is scaled (the default).</li>
     *   <li><code>LineScaleMode.NONE</code>: Never scale the line thickness.</li>
     *   <li><code>LineScaleMode.VERTICAL</code>: Do not scale the line thickness if the object is scaled vertically only. </li>
     *   <li><code>LineScaleMode.HORIZONTAL</code>: Do not scale the line thickness if the object is scaled horizontally only. </li>
     * </ul>
     * 
     * @param caps A value from the flash.display.CapsStyle class that specifies the type of 
     * caps at the end of lines. 
     * Valid values are: <code>CapsStyle.NONE</code>, <code>CapsStyle.ROUND</code>, 
     * and <code>CapsStyle.SQUARE</code>. If a value is not indicated, use round caps. 
     * 
     * @param joints A value from the flash.display.JointStyle class that specifies the type of 
     * joint appearance used at angles. 
     * Valid values are: <code>JointStyle.BEVEL</code>, <code>JointStyle.MITER</code>, and <code>JointStyle.ROUND</code>. 
     * If a value is not indicated, use round joints. 
     * 
     * @param miterLimit A number that indicates the limit at which a miter is cut off. 
     * Valid values range from 1 to 255 (and values outside that range are rounded to 1 or 255). 
     * This value is only used if the jointStyle is set to "miter". 
     * The <code>miterLimit</code> value represents the length that a miter can extend beyond the point at which 
     * the lines meet to form a joint. 
     * The value expresses a factor of the line thickness. 
     * For example, with a <code>miterLimit</code> factor of 2.5 and a thickness of 10 pixels, the miter is cut off at 25 pixels. 
     * 
     * @see flash.display.Graphics#lineStyle()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */    
    public function lineStyle(thickness:Number, color:uint = 0, alpha:Number = 1.0,
                              pixelHinting:Boolean = false, scaleMode:String = "normal",
                              caps:String = null, joints:String = null, miterLimit:Number = 3):void
    {
        borderWidth = thickness;
        pushOp(CartesianOpCode.LINE_STYLE, { thickness: thickness, color: color, alpha: alpha, pixelHinting: pixelHinting, scaleMode: scaleMode,
                                    caps: caps, joints: joints, miterLimit: miterLimit });
    }
    
    /**
     * Draws a line using the current line style from the current drawing position to (x, y); 
     * the current drawing position is then set to (x, y). 
     * If the display object in which you are drawing contains content that was created 
     * with the Flash drawing tools, calls to the <code>lineTo()</code> method are drawn underneath the content. 
     * If you call <code>lineTo()</code> before any calls to the moveTo() method, the default position 
     * for the current drawing is (0, 0). 
     * If any of the parameters are missing, this method fails and the current drawing 
     * position is not changed. Coordinates are in terms of data rather than screen coordinates.
     * The coordinates that you pass to this method are in terms of chart data rather than screen coordinates.
     * 
     * @param x The x coordinate of the drawing position.
     * 
     * @param y The y coordinate of the drawing position. 
     * 
     * @see flash.display.Graphics#lineTo()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function lineTo(x:*, y:*):void
    {
        pushOp(CartesianOpCode.LINE_TO, { x: x, y:y, borderWidth: borderWidth });
    }
    
    /**
     * Moves the current drawing position to (x, y). 
     * If any of the parameters are missing, this method fails and the current 
     * drawing position is not changed. 
     * The coordinates that you pass to this method are in terms of chart data rather than screen coordinates.
     * 
     * @param x The x coordinate of the drawing position.
     * 
     * @param y The y coordinate of the drawing position. 
     *
     * @see flash.display.Graphics#moveTo()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function moveTo(x:*, y:*):void
    {
        pushOp(CartesianOpCode.MOVE_TO, { x: x, y:y, borderWidth: borderWidth });
    }
    
    
    /**
     *  Calls the <code>updateTransform()</code> method of the canvas, if necessary.
     *  This method is called automatically by the canvas
     *  during the <code>commitProperties()</code> method, as necessary,
     *  but a derived canvas might call it explicitly
     *  if the generated values are needed at an explicit time.
     *  Filtering and transforming of data relies on specific values
     *  being calculated by the axes, which can in turn
     *  depend on the data that is displayed in the chart.
     *  Calling this function at the wrong time might result
     *  in extra work being done, if those values are updated.
     *  
     *  @return Returns <code>true</code> if <code>updateTransform()</code> was called. Otherwise, <code>false</code>.
     * 
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function validateTransform():Boolean
    {
        var updated:Boolean = false;
        if (dataTransform && _transformDirty)
        {
            updated = updateTransform();
        }
        return updated;
    }
    
    /**
     *  Calls the <code>updateMapping()</code> 
     *  and <code>updateFilter()</code> methods of the canvas, if necessary.
     *  This method is called automatically by the canvas
     *  from the <code>commitProperties()</code> method, as necessary,
     *  but a derived canvas might call it explicitly
     *  if the generated values are needed at an explicit time.
     *  Loading and mapping data against the axes is designed
     *  to be acceptable by the axes at any time.
     *  It is safe this method explicitly at any point.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function validateData():void
    {
        if (dataTransform)
        {
            if (_mappingDirty)
            {
                updateMapping();
            }
            if (_filterDirty)
            {
                updateFilter();
            }
        }
    }
    
    /**
     *  Called when the underlying data the canvas represents
     *  needs to be filtered against the ranges represented by the axes
     *  of the associated data transform.
     *  This can happen either because the underlying data has changed
     *  or because the range of the associated axes has changed.
     *  If you implement a custom canvas type, you should override this method
     *  and filter out any outlying data using the <code>filterCache()</code>
     *  method of the axes managed by its associated data transform.  
     *  The <code>filterCache()</code> method converts any values
     *  that are out of range to <code>NaN</code>.
     *  You must be sure to call the <code>super.updateFilter()</code> method
     *  in your subclass.
     *  You should not generally call this method directly.
     *  Instead, if you need to guarantee that your data has been filtered
     *  at a given point, call the <code>validateTransform()</code> method
     *  of the CartesianDataCanvas class.
     *  You can generally assume that your <code>updateData()</code>
     *  and <code>updateMapping()</code> methods have been called
     *  prior to this method, if necessary.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    
    protected function updateFilter():void
    {
        dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).filterCache(_dataCache.yCache,"mappedValue","filteredValue");
        dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).filterCache(_dataCache.xCache,"mappedValue","filteredValue");
        
        var n:int = _dataCache.xCache.length;
        for (var i:int = 0; i < n; i++)
        {
            if (isNaN(_dataCache.xCache[i].filteredValue))
                delete _dataCache.xMap[_dataCache.xCache[i].value];
        }
        
        n = _dataCache.yCache.length;
        for (i = 0; i < n; i++)
        {
            if (isNaN(_dataCache.yCache[i].filteredValue))
                delete _dataCache.yMap[_dataCache.yCache[i].value];
        }
        stripNaNs(_dataCache.xCache,"filteredValue");
        stripNaNs(_dataCache.yCache,"filteredValue");
        _filterDirty = false;
    }
    
    /**
     * @private
     */
    private function mapChildren():void
    {
        var width:Number;
        var height:Number;
        for (var p:* in _childMap)
        {
            var dc:CartesianDataChild = _childMap[p];
            if (dc.horizontalCenter != undefined)
            {
                width = widthFor(dc.child);
                split(dc.horizontalCenter)
                _dataCache.storeX(_data,width/2 - _offset,width/2 + _offset);
            }
            else if (dc.right == undefined)
            {
                split(dc.left);
                _dataCache.storeX(_data,- _offset,widthFor(dc.child) + _offset);
            }
            else if (dc.left == undefined)              
            {
                split(dc.right);
                _dataCache.storeX(_data,widthFor(dc.child) - _offset,_offset);
            }   
            else
            {
                split(dc.left);
                _dataCache.storeX(_data,-_offset,_offset);
                split(dc.right);
                _dataCache.storeX(_data,-_offset,_offset);
            }
            if (dc.verticalCenter != undefined)
            {
                height = heightFor(dc.child);
                split(dc.verticalCenter);
                _dataCache.storeY(_data,height/2 - _offset,height/2 + _offset);
            }
            else if (dc.bottom== undefined)
            {
                split(dc.top);
                _dataCache.storeY(_data,- _offset,heightFor(dc.child) + _offset);
            }
            else if (dc.top == undefined)               
            {
                split(dc.bottom);
                _dataCache.storeY(_data,heightFor(dc.child) - _offset,_offset);
            }
            else
            {
                split(dc.top);
                _dataCache.storeY(_data,-_offset,_offset);
                split(dc.bottom);
                _dataCache.storeY(_data,-_offset,_offset);
            }
        }
    }
    
    /**
     *  Removes any item from the provided cache whose <code>field</code>
     *  property is <code>NaN</code>.
     *  Derived classes can call this method from their updateFilter()
     * implementation to remove any ChartItems filtered out by the axes.
     *  
     *  @param cache An array of objects.
     * 
     *  @param field The <code>field</code> property test against.
     * 
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function stripNaNs(cache:Array /* of Object */, field:String):void
    {
        var len:int = cache.length;
        var start:int = -1;
        var end:int = -1;
        var i:int;
        var n:int;
        
        if (field == "")
        {
            n = cache.length;
            for (i = n - 1; i >= 0; i--)
            {
                if (isNaN(cache[i]))
                {
                    if (start < 0)
                    {
                        start = end = i;
                    }
                    else if (end - 1 == i)
                    {
                        end = i;
                    }
                    else
                    {
                        cache.splice(end, start - end + 1);
                        start = end = i;
                    }
                }
            }
        }
        else
        {
            n = cache.length;
            for (i = n - 1; i >= 0; i--)
            {
                if (isNaN(cache[i][field]))
                {
                    if (start < 0)
                    {
                        start = end = i;
                    }
                    else if (end - 1 == i)
                    {
                        end = i;
                    }
                    else
                    {
                        cache.splice(end, start - end + 1);
                        start = end = i;
                    }
                }
            }
        }

        if (start >= 0)
            cache.splice(end, start - end + 1);
    }
    
    /**
     *  Informs the canvas that the underlying data
     *  in the data provider has changed.
     *  This method triggers calls to the <code>updateMapping()</code>
     *  and <code>updateTransform()</code> methods on the next call
     *  to the <code>commitProperties()</code> method.
     *  
     *  @param invalid <code>true</code> if the data provider's data has changed.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function invalidateData(invalid:Boolean = true):void
    {
        if (invalid)
        {
            invalidateDisplayList();
        }
    }
        
    /**
     * @private
     * Takes our data values and converts them into pixel values.
     */
    protected function updateMapping():void
    {
        if (_dataCacheDirty)
        {
            _dataCache = new CartesianDataCache();
            
            var p:*;
            var record:*;
            var value:*;
            var boundedValue:BoundedValue;
            
            var n:int = _opCodes.length;
            for (var i:int = 0; i < n; i++)
            {
                _opCodes[i].collectValues(_dataCache);
            }

            mapChildren();
                
            _dataCache.xCache = [];
            _dataCache.yCache = [];
            _hDataDesc.min = Number.MAX_VALUE;
            _hDataDesc.max = Number.MIN_VALUE;
            _vDataDesc.min = Number.MAX_VALUE;
            _vDataDesc.max = Number.MIN_VALUE;
            for (p in _dataCache.xMap)
            {
                value = _dataCache.xMap[p];
                _dataCache.xCache.push({ value: value });             
            }
            for (p in _dataCache.yMap)
            {
                value = _dataCache.yMap[p];
                _dataCache.yCache.push({ value: value });             
            }
            _dataCacheDirty = false;
            _mappingDirty = true;
        }
        if (_mappingDirty)
        {
            dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).mapCache(_dataCache.yCache,"value","mappedValue",true);
            dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).mapCache(_dataCache.xCache,"value","mappedValue",true);
            _transformDirty = true;
            _mappingDirty = false;
            _filterDirty = true;
            var boundedValues:Array /* of BoundedValue */ = [];

            n = _dataCache.xCache.length;
            for (i = 0; i < n; i++)
            {
                value = _dataCache.xCache[i];
                boundedValue = _dataCache.xBoundedValues[value.value];
                if (boundedValue)
                {
                    boundedValue.value = value.mappedValue;
                    boundedValues.push(boundedValue);
                }
            }
            if (boundedValues.length > 0)
            {
                _hDataDesc.boundedValues = boundedValues;
                boundedValues = [];
            }
            
            n = _dataCache.yCache.length;
            for (i = 0; i < n; i++)
            {
                value = _dataCache.yCache[i];
                boundedValue = _dataCache.yBoundedValues[value.value];
                if (boundedValue)
                {
                    boundedValue.value = value.mappedValue;
                    boundedValues.push(boundedValue);
                }
            }
            if (boundedValues.length > 0)
            {
                _vDataDesc.boundedValues = boundedValues;
            }

            n = _dataCache.yCache.length;
            for (i = 0; i < n; i++)
            {
                record = _dataCache.yCache[i];
                _vDataDesc.min = Math.min(_vDataDesc.min, record.mappedValue);
                _vDataDesc.max = Math.max(_vDataDesc.max, record.mappedValue);
            }
            
            n = _dataCache.xCache.length;
            for (i = 0; i < n; i++)
            {
                record = _dataCache.xCache[i];
                _hDataDesc.min = Math.min(_hDataDesc.min, record.mappedValue);
                _hDataDesc.max = Math.max(_hDataDesc.max, record.mappedValue);
            }
            
        }
    }
    
    /**
     * @private
     */
    protected function updateTransform():Boolean
    {
        var record:Object;
        var updated:Boolean = false;
        if (_transformDirty == false)
        {
            if (unscaledHeight != _oldUW || unscaledWidth != _oldUW)
            {
                _transformDirty = true;
                _oldUW = unscaledWidth;
                _oldUH = unscaledHeight;
            }
        }

        if (_transformDirty)
        {
            updated = true;
            _transformDirty = false;
            dataTransform.transformCache(_dataCache.xCache,"mappedValue","pixelValue",null,null);           
            dataTransform.transformCache(_dataCache.yCache,null,null,"mappedValue","pixelValue");           
    
            var n:int = _dataCache.xCache.length;
            for (var i:int = 0; i < n; i++)
            {
                record = _dataCache.xCache[i];
                _dataCache.xMap[record.value] = record.pixelValue;
            }
            
            n = _dataCache.yCache.length;
            for (i = 0; i < n; i++)
            {
                record = _dataCache.yCache[i];
                _dataCache.yMap[record.value] = record.pixelValue;
            }
        }
        return updated;
    }
  
    /**
     * @private
     * Retrieves value and offset from given data
     */
    private function split(v:*):void
    {
        if (v is CartesianCanvasValue)
        {
            _data = v.value;
            _offset = v.offset;
            if (isNaN(_offset))
                _offset = 0;
        }
        else
        {
            _data = v;
            _offset = 0;
        }
    }
    
    /**
     * @private
     * Retrieves value from given data
     */
    private function data(v:*):*
    {
        if (v is CartesianCanvasValue)
            return v.value;
        else
            return v;
    }
    
    /**
     * @private
     * Retrieves offset from given data
     */
    private function offset(v:*):*
    {
        if (v is CartesianCanvasValue)
            return v.offset;
        else
            return 0;
    }
        
    /**
     * @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */ 
    // this function is called by the charting package when the axes that affect this element change their mapping some how.
    // that means we need to call the mapCache function again to get new mappings.  
    override public function mappingChanged():void
    {
        invalidateDisplayList();
    }
    
    /**
     * @private
     */
    private function widthFor(child:DisplayObject):Number
    {
        return  (child is IUIComponent)? IUIComponent(child).getExplicitOrMeasuredWidth() + 2:
                (child is IFlexDisplayObject)? IFlexDisplayObject(child).measuredWidth + 2:
                child.width; 
    }

    /**
     * @private
     */
    private function heightFor(child:DisplayObject):Number
    {
        return  (child is IUIComponent)? IUIComponent(child).getExplicitOrMeasuredHeight() + 2:
                (child is IFlexDisplayObject)? IFlexDisplayObject(child).measuredHeight + 2:
                child.height; 
    }
    
    /**
     * private
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    private function positionChildren():void
    {
        for (var p:* in _childMap)
        {
            var dc:CartesianDataChild = _childMap[p];
            var left:Number;
            var right:Number;
            var top:Number;
            var bottom:Number;
            var hCenter:Number;
            var vCenter:Number;
            var width:Number;
            var height:Number;
            
            if (dc.horizontalCenter != undefined)
            {
                hCenter = _dataCache.x(data(dc.horizontalCenter)) + offset(dc.horizontalCenter);
                width = widthFor(dc.child);
                left = hCenter - width/2;
                right = hCenter + width/2;      
            }
            else if (dc.right == undefined)
            {
                left = _dataCache.x(data(dc.left)) + offset(dc.left);
                right = left + widthFor(dc.child);
            }
            else if (dc.left == undefined)              
            {
                right = _dataCache.x(data(dc.right)) + offset(dc.right);
                left = right - widthFor(dc.child);
            }
            else
            {
                left = _dataCache.x(data(dc.left)) + offset(dc.left);
                right = _dataCache.x(data(dc.right)) + offset(dc.right);
            }
            
            if (dc.verticalCenter != undefined)
            {
                vCenter = _dataCache.y(data(dc.verticalCenter)) + offset(dc.verticalCenter);
                height = heightFor(dc.child);
                top = vCenter - height/2;
                bottom= vCenter + height/2;     
            }
            else if (dc.bottom == undefined)
            {
                top = _dataCache.y(data(dc.top)) + offset(dc.top);
                bottom = top + heightFor(dc.child);
            }
            else if (dc.top == undefined)               
            {
                bottom = _dataCache.y(data(dc.bottom)) + offset(dc.bottom);
                top = bottom - heightFor(dc.child);
            }
            else
            {
                top = _dataCache.y(data(dc.top)) + offset(dc.top);
                bottom = _dataCache.y(data(dc.bottom)) + offset(dc.bottom);
            }
            if (isNaN(left) || isNaN(right) || isNaN(top) || isNaN(bottom))
            {
                removeChild(p);
                continue;
            }
            if (dc.child is IFlexDisplayObject)
            {
                IFlexDisplayObject(dc.child).setActualSize(right-left,bottom-top);
                IFlexDisplayObject(dc.child).move(left, top);
            }
            else
            {
                dc.child.width = right - left;
                dc.child.height = bottom - top;
                dc.child.x = left;
                dc.child.y = top;
            }
        }
    }     

    /**
     * @private
     */
    private function invalidateOpCodes():void
    {
        dataChanged();
        invalidateDisplayList();
    }
    
    /**
     * @private
     */
    private function pushOp(code:int, params:Object = null):CartesianOpCode
    {
        var op:CartesianOpCode = new CartesianOpCode(this,code,params);
        _opCodes.push(op);
        invalidateOpCodes();
        return op;
    }
}

}

import mx.charts.chartClasses.CartesianDataCanvas;
import flash.utils.Dictionary;
import mx.charts.chartClasses.BoundedValue;
import flash.geom.Matrix;
import flash.display.Graphics;
import mx.charts.chartClasses.CartesianCanvasValue;
import mx.core.mx_internal;

use namespace mx_internal;

class CartesianOpCode
{
    public var canvas:CartesianDataCanvas;
    public var code:int;
    public var params:Object;
    
    public function CartesianOpCode(canvas:CartesianDataCanvas,code:int, params:Object = null):void
    {
        this.canvas = canvas;
        this.code = code;
        this.params = (params == null) ? {} : params;
    }
    
    public static const BEGIN_BITMAP_FILL:int =     0;
    public static const BEGIN_FILL:int =            1;
    public static const CURVE_TO:int =              2;
    public static const DRAW_CIRCLE:int =           3;
    public static const DRAW_ELLIPSE:int =          4;
    public static const DRAW_RECT:int =             5;
    public static const DRAW_ROUNDRECT:int =        6;  
    public static const END_FILL:int =              7;
    public static const LINE_STYLE:int =            8;
    public static const LINE_TO:int =               9;
    public static const MOVE_TO:int =               10;
    
    private var _data:*;
    private var _offset:Number;

    private function split(v:*):void
    {
        if (v is CartesianCanvasValue)
        {
            _data = v.value;
            _offset = v.offset;
            if (isNaN(_offset))
                _offset = 0;
        }
        else
        {
            _data = v;
            _offset = 0;
        }
    }
    
    private function data(v:*):*
    {
        if (v is CartesianCanvasValue)
            return v.value;
        else
            return v;
    }
    
    private function offset(v:*):*
    {
        if (v is CartesianCanvasValue)
            return v.offset;
        else
            return 0;
    }
    
    mx_internal function collectValues(cache:CartesianDataCache):void
    {
        switch (code)
        {
            case BEGIN_BITMAP_FILL:
                split(params.x);
                if (_data != undefined)
                    cache.storeX(_data,-_offset,_offset);
                split(params.y);
                if (_data != undefined)
                    cache.storeY(_data,-_offset,_offset);
                break;
            case CURVE_TO:
                split(params.anchorX);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.anchorY);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.controlX);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.controlY);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                break;
            case DRAW_CIRCLE:
                split(params.x);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.y);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                break;
            case MOVE_TO:
            case LINE_TO:
                split(params.x);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.y);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                break;
            case DRAW_ELLIPSE:
                split(params.left);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.top);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.right);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.bottom);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                break;
            case DRAW_RECT:
                split(params.left);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.top);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.right);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.bottom);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                break;
            case DRAW_ROUNDRECT:
                split(params.left);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.top);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.right);
                cache.storeX(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                split(params.bottom);
                cache.storeY(_data,-_offset+params.borderWidth/2,_offset+params.borderWidth/2);
                break;
        }
    }

    mx_internal function render(target:CartesianDataCanvas,cache:CartesianDataCache):void
    {
        var left:Number;
        var top:Number;
        var right:Number;
        var bottom:Number;
        var controlX:Number;
        var controlY:Number;
        var anchorX:Number;
        var anchorY:Number;
        var x:Number;
        var y:Number;
        
        var g:Graphics = target.graphics;
            switch (code)
        {
            case BEGIN_BITMAP_FILL:
                var m:Matrix;
                if (!(params.matrix))
                    m = new Matrix();
                else
                    m = params.matrix.clone();
                    
                var d:* = data(params.x);
                if (d != undefined)
                    m.tx = cache.x(d);
                m.tx  += offset(params.x);
                d = data(params.y);
                if (d != undefined)
                    m.ty = cache.y(d);
                m.ty += offset(params.y);
                g.beginBitmapFill(params.bitmap, m, params.repeat, params.smooth);
                break;
            
            case BEGIN_FILL:
                g.beginFill(params.color, params.alpha);
                break;              
            
            case CURVE_TO:
                controlX = cache.x(data(params.controlX)) + offset(params.controlX);
                controlY = cache.y(data(params.controlY)) + offset(params.controlY);
                anchorX = cache.x(data(params.anchorX)) + offset(params.anchorX);
                anchorY = cache.y(data(params.anchorY)) + offset(params.anchorY);
                if (isNaN(controlX) || isNaN(controlY) || isNaN(anchorX) || isNaN(anchorY))
                    return;
                g.curveTo(controlX, controlY, anchorX, anchorY);
                break;
            
            case DRAW_CIRCLE:
                x = cache.x(data(params.x)) + offset(params.x);
                y = cache.y(data(params.y)) + offset(params.y);
                if (isNaN(x) || isNaN(y))
                    return;
                 g.drawCircle(x, y, params.radius);
                break;
            
            case DRAW_ELLIPSE:
                left = cache.x(data(params.left)) + offset(params.left);
                top = cache.y(data(params.top)) + offset(params.top);
                right = cache.x(data(params.right)) + offset(params.right);
                bottom = cache.y(data(params.bottom)) + offset(params.bottom);
                if (isNaN(left) || isNaN(top) || isNaN(right) || isNaN(bottom))
                    return;
                g.drawEllipse(left, top, 
                            right - left,bottom - top);
                break;
                
            case DRAW_RECT:
                left = cache.x(data(params.left)) + offset(params.left);
                top = cache.y(data(params.top)) + offset(params.top);
                right = cache.x(data(params.right)) + offset(params.right);
                bottom = cache.y(data(params.bottom)) + offset(params.bottom);
                if (isNaN(left) || isNaN(top) || isNaN(right) || isNaN(bottom))
                    return;
                g.drawRect(left, top, 
                            right - left,bottom - top);
                break;
                
            case DRAW_ROUNDRECT:
                left = cache.x(data(params.left)) + offset(params.left);
                top = cache.y(data(params.top)) + offset(params.top);
                right = cache.x(data(params.right)) + offset(params.right);
                bottom = cache.y(data(params.bottom)) + offset(params.bottom);
                if (isNaN(left) || isNaN(top) || isNaN(right) || isNaN(bottom))
                    return;
                g.drawRoundRect(left, top, 
                            right - left,bottom - top,params.cornerRadius,params.cornerRadius);
                break;
                
            case END_FILL:
                g.endFill();
                break;
                    
            case LINE_STYLE:
                g.lineStyle(params.thickness,params.color,params.alpha,params.pixleHinting,params.scaleMode,params.caps,params.joints,params.miterLimit);
                break;
                
            case MOVE_TO:
                x = cache.x(data(params.x)) + offset(params.x);
                y = cache.y(data(params.y)) + offset(params.y);
                if (isNaN(x) || isNaN(y))
                    return;
                g.moveTo(x , y);
                break;
                
            case LINE_TO:
                x = cache.x(data(params.x)) + offset(params.x);
                y = cache.y(data(params.y)) + offset(params.y);
                if (isNaN(x) || isNaN(y))
                    return;
                g.lineTo(x,y);
                break;          
        }
    }
}

import mx.core.mx_internal;

use namespace mx_internal;

class CartesianDataCache
{
    public var xCache:Array /* of Object */;
    public var yCache:Array /* of Object */;
    
    public var xBoundedValues:Dictionary;
    public var yBoundedValues:Dictionary;
    public var xMap:Dictionary;
    public var yMap:Dictionary;
    
    public function CartesianDataCache():void
    {
        xMap = new Dictionary(true);
        yMap = new Dictionary(true);
        xCache = [];
        yCache = [];
        xBoundedValues = new Dictionary(true);
        yBoundedValues = new Dictionary(true);
    }
    
    mx_internal function storeX(value:*,leftMargin:Number = 0, rightMargin:Number = 0):void
    {
        var bounds:BoundedValue;

        if (leftMargin < 0)
            leftMargin = 0;
        if (rightMargin < 0)
            rightMargin = 0;
            
        xMap[value] = value;
        if (leftMargin != 0 || rightMargin != 0)
        bounds = xBoundedValues[value];
        if (leftMargin > 0)
            leftMargin += 2;
        if (rightMargin > 0)
            rightMargin += 2;
            
        if (!bounds)
        {
            xBoundedValues[value] = bounds = new BoundedValue(0,leftMargin,rightMargin);
        }
        else
        {
            bounds.lowerMargin = Math.max(bounds.lowerMargin,leftMargin);
            bounds.upperMargin = Math.max(bounds.upperMargin,rightMargin);
        }
    }

    mx_internal function storeY(value:*,topMargin:Number = 0,bottomMargin:Number = 0):void
    {
        var bounds:BoundedValue;

        yMap[value] = value;
        if (topMargin != 0 || bottomMargin != 0)
        {
            bounds = yBoundedValues[value];
            if (!bounds)
            {
                yBoundedValues[value] = bounds = new BoundedValue(0,bottomMargin,topMargin);
            }
            else
            {
                bounds.lowerMargin = Math.max(bounds.lowerMargin,bottomMargin);
                bounds.upperMargin = Math.max(bounds.upperMargin,topMargin);
            }
        }
    }
    
    mx_internal function x(value:*):Number
    {
        return Number(xMap[value]);
    }
    
    mx_internal function y(value:*):Number
    {
        return Number(yMap[value]);
    }
}

import flash.display.DisplayObject;
import flash.events.EventDispatcher;
import flash.events.Event;
use namespace mx_internal;
    
[DefaultProperty("content")]
[Event("change")]

class CartesianDataChild extends EventDispatcher
{
    public function CartesianDataChild(child:DisplayObject = null,left:* = undefined, top:* = undefined, right:* = undefined, bottom:* = undefined,
    horizontalCenter:* = undefined, verticalCenter:* = undefined):void
    {
        this.child = child;
        this.left = left;
        this.top = top;
        this.bottom = bottom;
        this.right = right;
    }
    
    public var child:DisplayObject;
    public var left:*;
    public var right:*;
    public var top:*;
    public var bottom:*;        
    public var horizontalCenter:*;
    public var verticalCenter:*;
    
    public function set content(value:*):void
    {
        if (value is DisplayObject)
            child = value;
        else if (value is Class)
            child = new value();
        dispatchEvent(new Event("change"));
    }       
}
