////////////////////////////////////////////////////////////////////////////////
//
//  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.series
{

import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.filters.DropShadowFilter;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;

import mx.charts.HitData;
import mx.charts.chartClasses.DataDescription;
import mx.charts.chartClasses.GraphicsUtilities;
import mx.charts.chartClasses.IAxis;
import mx.charts.chartClasses.InstanceCache;
import mx.charts.chartClasses.LegendData;
import mx.charts.chartClasses.PolarChart;
import mx.charts.chartClasses.PolarTransform;
import mx.charts.chartClasses.Series;
import mx.charts.renderers.WedgeItemRenderer;
import mx.charts.series.items.PieSeriesItem;
import mx.charts.series.renderData.PieSeriesRenderData;
import mx.charts.styles.HaloDefaults;
import mx.collections.CursorBookmark;
import mx.core.ClassFactory;
import mx.core.ContextualClassFactory;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.IFlexModuleFactory;
import mx.core.IUITextField;
import mx.core.LayoutDirection;
import mx.core.UIComponent;
import mx.core.UITextField;
import mx.core.mx_internal;
import mx.graphics.IFill;
import mx.graphics.IStroke;
import mx.graphics.SolidColor;
import mx.graphics.SolidColorStroke;
import mx.graphics.Stroke;
import mx.styles.CSSStyleDeclaration;
import mx.styles.ISimpleStyleClient;

use namespace mx_internal;

include "../styles/metadata/ItemRendererStyles.as"
include "../styles/metadata/TextStyles.as"

/**
 *  Specifies how much space, in pixels, to insert between the edge
 *  of the pie and the labels when rendering callouts.
 *  
 *  @default 10
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="calloutGap", type="Number", format="Length", inherit="no")]

/**
 *  Specifies the line style used to draw the lines to callouts.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="calloutStroke", type="mx.graphics.IStroke", inherit="no")]
    
/**
 *  Specifies an array of fill objects used to render
 *  each wedge of the PieChart control.
 *  If you do not provide enough Array elements for every slice,
 *  Flex repeats the fill from the beginning of the Array.
 *  If you specify a method using the <code>fillFunction</code> property, the 
 *  values set by that method take precedence over this Array.
 *  
 *  <p>To set the value of this property using CSS:
 *   <pre>
 *    PieSeries {
 *      fills:#CC66FF, #9966CC, #9999CC;
 *    }
 *   </pre>
 *  </p>
 *  
 *  <p>To set the value of this property using MXML:
 *   <pre>
 *    &lt;mx:PieSeries ... &gt;
 *     &lt;mx:fills&gt;
 *      &lt;mx:SolidColor color="0xCC66FF"/&gt;
 *      &lt;mx:SolidColor color="0x9966CC"/&gt;
 *      &lt;mx:SolidColor color="0x9999CC"/&gt;
 *     &lt;/mx:fills&gt;
 *    &lt;/mx:PieSeries&gt;
 *   </pre>
 *  </p>
 *  
 *  <p>
 *  If you specify the <code>fills</code> property and you
 *  want to have a Legend control, you must manually create a Legend control and 
 *  add LegendItems to it.
 *  </p>
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="fills", type="Array", arrayType="mx.graphics.IFill", inherit="no")]

/**
 *  A number from 0 to 1 specifying the distance from the center of the series
 *  to the inner edge of the rendered wedges,
 *  as a percentage of the total radius assigned to the series.
 *  This property is assigned directly to the series.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="innerRadius", type="Number", inherit="no")]
    
/**
 *  Specifies the font size threshold, in points,
 *  below which inside labels are considered illegible.
 *  Below this threshold, Flex either removes labels entirely
 *  or renders them as callouts based on the setting of
 *  the <code>labelPosition</code> property.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="insideLabelSizeLimit", type="Number", inherit="no")]

/** 
 *  Specifies how to render value labels.
 *  You can set this property in MXML or using styles.
 *  Valid values are:
 *  <ul>
 *    <li><code>"none"</code> - 
 *    Do not draw labels.</li>
 *  
 *    <li><code>"outside"</code> - 
 *    Draw labels around the boundary of the pie.</li>
 *  
 *    <li><code>"callout"</code> - 
 *    Draw labels in two vertical stacks on either side of the pie.
 *    The pie is shrunk if necessary to make room for the labels
 *    (see <code>maxLabelRadius</code>).
 *    Draw key lines from each label to the associated wedge.
 *    Shrink labels as necessary to fit the space provided.</li>
 *  
 *    <li><code>"inside"</code> - 
 *    Draw labels inside the chart,
 *    centered approximately seven tenths of the way along each wedge.
 *    Shrink labels to ensure that they do not interfere with each other.
 *    If labels are shrunk below the <code>insideLabelSizeLimitSize</code>
 *    property, remove them.
 *    When two labels overlap, Flex gives priority
 *    to labels for larger slices.</li>
 *  
 *    <li><code>"insideWithCallout"</code> - 
 *    Draw labels inside the pie, but if labels are shrunk
 *    below a legible size, Flex converts them to callouts.</li>
 *  </ul>
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="labelPosition", type="String", enumeration="none,outside,callout,inside,insideWithCallout", inherit="no")]

/**
 *  Specifies the line style used to draw the border
 *  between the wedges of the pie.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="radialStroke", type="mx.graphics.IStroke", inherit="no")]

/**
 *  Sets the direction in which the series is rendered.
 *  Valid values are:
 *  <ul>
 *    <li><code>"clockwise"</code> - 
 *    Draw the wedges in clockwise direction.</li>
 *  
 *    <li><code>"counterClockwise"</code> - 
 *    Draw the wedges in anti-clockwise direction.</li>
 *  </ul>
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 4
 */
[Style(name="renderDirection", type="String", enumeration="clockwise,counterClockwise", inherit="no")]

/**
 *  Sets the stroke style for this data series.
 *  You must specify a Stroke object to define the stroke. 
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="stroke", type="mx.graphics.IStroke", inherit="no")]

/**
 *  The PieSeries class defines the data series for a PieChart control.
 *  The default item renderer is the WedgeItemRenderer class.
 *  Optionally, you can define an itemRenderer for the data series.
 *  The itemRenderer must implement the IDataRenderer interface.
 *
 *  @mxml
 *  
 *  <p>The <code>&lt;mx:PieSeries&gt;</code> tag inherits all the properties
 *  of its parent classes, and the following properties:</p>
 *  
 *  <pre>
 *  &lt;mx:PieSeries
 *    <strong>Properties</strong>
 *    angularAxis=""
 *    explodeRadius="0"
 *    field="null"
 *    fillFunction="<i>Internal fill function</i>"
 *    itemType="<i>No default</i>"
 *    labelFunction="<i>No default</i>"
 *    labelField="<i>No default</i>"
 *    maxLabelRadius="0.6" 
 *    nameField="null"
 *    outerRadius="1"
 *    perWedgeExplodeRadius="<i>Array, no default</i>"
 *    renderDataType="<i>No default</i>"
 *    reserveExplodeRadius="0"
 *    startAngle="0"
 * 
 *    <strong>Styles</strong>
 *    calloutGap="10"
 *    calloutStroke="<i>IStroke; no default</i>"
 *    fills="<i>IFill; no default</i>"
 *    fontSize="10"
 *    innerRadius="0"
 *    insideLabelSizeLimit="9"
 *    itemRenderer="<i>No default</i>"
 *    labelPosition="none|callout|inside|insideWithCallout|outside"
 *    legendMarkerRenderer="<i>Defaults to series's itemRenderer</i>"
 *    radialStroke="<i>IStroke; no default</i>"
 *    renderDirection="clockwise|counterClockwise"
 *    stroke="<i>IStroke; no default</i>"
 *  /&gt;
 *  </pre>
 *  
 *  @see mx.charts.PieChart
 *  
 *  @includeExample ../examples/PieChartExample.mxml
 *  
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class PieSeries extends Series
{
    include "../../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Class initialization
    //
    //--------------------------------------------------------------------------

    //--------------------------------------------------------------------------
    //
    //  Class constants
    //
    //--------------------------------------------------------------------------
    
    /**
     *  private
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    private static const DROP_SHADOW_SIZE:Number = 6;

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function PieSeries()
    {
        super();

        _labelLayer = new UIComponent();
        _labelLayer.styleName = this;
        
        _labelCache = new InstanceCache(UITextField,_labelLayer);
        
        _labelCache.discard = true;
        _labelCache.remove = true;
        
        _labelCache.properties =
        {
            autoSize: TextFieldAutoSize.LEFT,
            selectable: false,
            styleName: this
        };
        
        perWedgeExplodeRadius = [];

        _instanceCache = new InstanceCache(null, this);
        _instanceCache.properties = { styleName: this };
		filters = [ new DropShadowFilter(DROP_SHADOW_SIZE, 45, 0, 60,
                                         DROP_SHADOW_SIZE, DROP_SHADOW_SIZE) ];
        
        dataTransform = new PolarTransform();                                 
    }

    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private var _moduleFactoryInitialized:Boolean = false; 
	
    /**
     *  @private
     */
    private var _instanceCache:InstanceCache;

    /**
     *  @private
     */
    private var _renderData:PieSeriesRenderData;

    /**
     *  @private
     */
    private var _measuringField:IUITextField;
    
    /**
     *  @private
     */
    private var _labelLayer:UIComponent;
    
    /**
     *  @private
     */
    private var _labelCache:InstanceCache;

    /**
     *  @private
     */
    private var _origin:Point;

    /**
     *  @private
     */
    private var _radiusInPixelsAfterLabels:Number;
    
    /**
     *  @private
     */
    private var _radiusInPixelsScaledForExplode:Number = 1;
    
    /**
     *  @private
     */
    private var _innerRadiusInPixels:Number = 0;
    
    /**
     *  @private
     */
    private var _innerRadiusInPixelsScaledForExplode:Number = 0;
    
    /**
     *  @private
     */
    private var _maxExplodeRadiusRatio:Number = 0;
    
    /**
     *  @private
     */
    private var _localFills:Array /* of IFill */;
    
    /**
     *  @private
     */
    private var _fillCount:int;
    
    /**
     *  @private
     */
    private var _bAxesDirty:Boolean = false;
 	

    //--------------------------------------------------------------------------
    //
    //  Overridden properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  items
    //----------------------------------

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function get items():Array /* of PieSeriesItem */
    {
        return _renderData ? _renderData.filteredCache : null;
    }

    //----------------------------------
    //  labelContainer
    //----------------------------------

    /**
     *  @private
     */
    override public function get labelContainer():Sprite
    {
        return _labelLayer;
    }

    //----------------------------------
    //  legendData
    //----------------------------------

    /**
     *  @private
     */
    override public function get legendData():Array /* of LegendData */
    {
        validateData();

        var keyItems:Array /* of LegendData */ = [];
        var localFills:Array /* of IFill */ = getStyle("fills");
        var labelText:String;
        var i:int = 0;
        var legendData:PieSeriesLegendData;
        var markerFactory:IFactory = getStyle("legendMarkerRenderer");

        // This function can get called
        // before the style chain is properly initialized.
        if (!localFills)
            localFills = [ new SolidColor(0) ];

        var cache:Array /* of PieSeriesItem */ = _renderData.filteredCache ?
                          _renderData.filteredCache :
                          _renderData.cache;
        
        var n:int = cache.length;
        for (i = 0; i < n; i++)
        {
            var current:PieSeriesItem = cache[i];

            legendData = new PieSeriesLegendData();
            
            legendData.fill = current.fill;
            
            if (_nameField != null && _nameField != "")
                legendData.label = current.item[_nameField];
            else
                legendData.label = null;
            
            legendData.element = this;
            
            if (markerFactory) 
            {
                legendData.marker = markerFactory.newInstance();
                if (legendData.marker is ISimpleStyleClient)
                    (legendData.marker as ISimpleStyleClient).styleName = this;
            }
            
            keyItems[i] = legendData;
        }

        return keyItems;
    }

    //----------------------------------
    //  renderData
    //----------------------------------

    /**
     *  @private
     */
    override protected function get renderData():Object
    {
        if (!_renderData)
        {
            var renderDataType:Class = this.renderDataType;
            var td:PieSeriesRenderData = new renderDataType();
            td.cache = td.filteredCache = [];
            return td;
        }

        return _renderData;
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

	//----------------------------------
    //  angularAxis
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the angularAxis property.
     */
    private var _angularAxis:IAxis;

    [Inspectable(category="Data")]
    
    /**
     *  The axis object used to map data values to an angle
     *  between 0 and 2 * PI.
     *  By default, this is a linear axis with the <code>autoAdjust</code>
     *  property set to <code>false</code>.
     *  So, data values are mapped uniformly around the chart.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get angularAxis():IAxis
    {
        return _angularAxis;
    }   
    
    /**
     *  @private
     */
    public function set angularAxis(value:IAxis):void
    {
        _angularAxis = value;
        _bAxesDirty = true;

        invalidateData();
    }   

    //----------------------------------
    //  explodeRadius
    //----------------------------------

    /**
     *  @private
     *  Storage for the explodeRadius property.
     */
    private var _explodeRadius:Number = 0;
    
    [Inspectable(category="General")]

    /**
     *  A number from 0 to 1, specifying how far all wedges of the pie
     *  series should be exploded from the center of the chart
     *  as a percentage of the total radius. 
     *  
     *  <p>This value explodes all wedges in the pie series uniformly.
     *  You can set the value for individual wedges
     *  via the <code>perWedgeExplodeRadius</code> property.</p>
     *  
     *  <p>The explode radius for any individual wedge is the value of the 
     *  <code>explodeRadius</code> property plus its value
     *  in the <code>perWedgeExplodeRadius</code> Array.</p>
     *  
     *  <p>To guarantee that the pie series
     *  stays within its containing chart's bounds while pulling out wedges,
     *  the pie series might shrink the total radius of the pie.
     *  If you dynamically pull out wedges at run time,
     *  the total pie radius shrinks. 
     *  To avoid this effect, you can "reserve" the space
     *  and shrink the total radius initially by setting the
     *  <code>reserveExplodeRadius</code> property to the maximum explode radius
     *  you intend to set at run time.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get explodeRadius():Number
    {
        return _explodeRadius;
    }

    /**
     *  @private
     */
    public function set explodeRadius(value:Number):void
    {
        _explodeRadius = Math.max(0,Math.min(value, 1));
        
        invalidateData();
    }

    //----------------------------------
    //  field
    //----------------------------------

    /**
     *  @private
     *  Storage for the field property.
     */
    private var _field:String = "";
    
    [Inspectable(category="General")]

    /**
     *  Specifies the field of the data provider that determines
     *  the data for each wedge of the PieChart control.
     * 
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get field():String
    {
        return _field;
    }

    /**
     *  @private
     */
    public function set field(value:String):void
    {
        _field = value;
        
        dataChanged();
    }

	
 	//--------------------------------------
    // fillFunction
    //--------------------------------------
    
    [Bindable]
    [Inspectable(category="General")]
    
    /**
     * @private
     * Storage for fillFunction property
     */
    private var _fillFunction:Function=defaultFillFunction;
    
    
    /**
     * Specifies a method that returns the fill for the current chart item in the series.
     * If this property is set, the return value of the custom fill function takes precedence over the 
     * <code>fills</code> style property.
     * But if it returns null, then <code>fills</code> will be prefered.
     * 
     * <p>The custom <code>fillFunction</code> has the following signature:
     *  
     * <pre>
     * <i>function_name</i> (item:ChartItem, index:Number):IFill { ... }
     * </pre>
     * 
     * <code>item</code> is a reference to the chart item that is being rendered.
     * <code>index</code> is the index of the chart item in the renderData's cache. This is different
     * from the index of the chart's data provider because it is sorted based on the x, y, and z values.
     * This function returns an object that implements the <code>IFill</code> interface.
     * </p>
     *  
     * <p>An example usage of a customized <code>fillFunction</code> is to return a fill
     * based on some threshold.</p>
     *   
     * @example
     * <pre>
     * public function myFillFunction(item:ChartItem, index:Number):IFill {
     *      var curItem:PieSeriesItem = PieSeriesItem(item);
     *      if (curItem.number > 10)
     *          return(new SolidColor(0x123456, .75));
     *      else
     *          return(new SolidColor(0x563412, .75));
     * }
     * </pre>
     *   
     * <p>
     *  If you specify a custom fill function for your chart series and you
     *  want to have a Legend control, you must manually create a Legend control and 
     *  add LegendItems to it.
     *  </p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get fillFunction():Function
    {
        return _fillFunction;
    }
    
    /**
     * @private
     */ 
    public function set fillFunction(value:Function):void
    {
        if (value == _fillFunction)
            return;
            
        if (value != null)
            _fillFunction=value;
        else
            _fillFunction=defaultFillFunction;
            
        invalidateDisplayList();
        legendDataChanged();
    }
    
    //----------------------------------
    //  itemType
    //----------------------------------

    [Inspectable(environment="none")]

    /**
     *  The subtype of ChartItem used by this series
     *  to represent individual items.
     *  Subclasses can override and return a more specialized class
     *  if they need to store additional information in the items.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function get itemType():Class
    {
        return PieSeriesItem;
    }

    //----------------------------------
    //  labelFunction
    //----------------------------------

    /**
     *  @private
     *  Storage for the labelFunction property.
     */
    private var _labelFunction:Function;

    [Inspectable(category="General")]

    /**
     *  Specifies a callback function used to render each label
     *  of the PieSeries.
     *  
     *  The callback function has the following format:
     *  <pre>
     *  <i>function_name</i>(<i>data</i>:Object, <i>field</i>:String, <i>index</i>:Number, <i>percentValue</i>:Number):String { ... }
     *  </pre>
     * 
     *  The <code>data</code> Object is the dataProvider item being rendered.
     *
     *  The <code>field</code> String is the name of the field in the data that is being rendered.
     *
     *  The <code>index</code> Number is the index in the original dataProvider of the item being rendered.
     *
     *  The <code>percent</code> Number is the percentage of the total this item represents.
     *
     *  This function returns a String that is the label for this item.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get labelFunction():Function
    {
        return _labelFunction;
    }

    /**
     *  @private
     */
    public function set labelFunction(value:Function):void
    {
        _labelFunction = value;

        invalidateDisplayList();
    }
    
    //---------------------------------
    // labelField
    //---------------------------------
    
    /**
     * @private
     * Storage for labelField property
     */
    private var _labelField:String;
    
    [Inspectable(category="General")]
    
    /**
     * Name of a field in the data provider whose value appears as label
     * Ignored if labelFunction is specified
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get labelField():String
    {
        return _labelField;
    }
       
    /**
     * @private
     */
    public function set labelField(value:String):void
    {
        _labelField = value;
        invalidateTransform();              
    }

    //----------------------------------
    //  maxLabelRadius
    //----------------------------------

    [Inspectable(category="General")]
    
    /**
     *  The maximum amount of the PieSeries's radius
     *  that can be allocated to labels.
     *  This value is only applicable when the series
     *  is rendering callout labels.
     *  
     *  <p>When rendering callout labels, the PieSeries reduces the radius
     *  of the wedges to allow space for the labels along the sides.
     *  Once the amount of space allocated to the labels reaches this limit,
     *  the series begins reducing the size of the labels
     *  to stay within this size.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public var maxLabelRadius:Number = 0.6;

    //----------------------------------
    //  nameField
    //----------------------------------

    /**
     *  @private
     *  Storage for the nameField property.
     */
    private var _nameField:String = "";

    [Inspectable(category="General")]

    /**
     *  Specifies the field of the data provider that determines
     *  the name of each wedge of the PieChart control.
     *  
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get nameField():String
    {
        return _nameField;
    }

    /**
     *  @private
     */
    public function set nameField(value:String):void
    {
        _nameField = value;

        dataChanged();

        legendDataChanged();
    }

    //----------------------------------
    //  outerRadius
    //----------------------------------

    /**
     *  @private
     *  Storage for the outerRadius property.
     */
    private var _outerRadius:Number = 1;
    
    [Inspectable(category="General")]
    
    /**
     *  The percentage of the total space available to the PieSeries
     *  to use when rendering the contents of the series.
     *  This value is managed by the containing chart,
     *  and should not be assigned to directly.
     *  <p>This value ranges from 0 to 1.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get outerRadius():Number
    {
        return _outerRadius;
    }

    /**
     *  @private
     */
    public function set outerRadius(value:Number):void
    {
        _outerRadius = value;

        invalidateDisplayList();
    }

    //----------------------------------
    //  perWedgeExplodeRadius
    //----------------------------------

    /**
     *  @private
     *  Storage for the perWedgeExplodeRadius property.
     */
    private var _perWedgeExplodeRadius:Array /* of Number */;
    
    [Inspectable(category="General", arrayType="Number")]

    /**
     *  An Array of numbers from 0 to 1, specifying how far each wedge
     *  of the pie series should be exploded from the center of the chart
     *  as a percentage of the total radius. 
     *  
     *  <p>The <i>n</i>th value in this Array corresponds to the <i>n</i>th pie wedge.
     *  For example, to pull the third wedge half way out,
     *  you assign <code>perWedgeExplodeRadius = [ 0, 0, 50];</code>.</p>
     *  
     *  <p><code>null</code> or missing values in the Array are treated as 0.</p>
     *  
     *  <p>To guarantee that the pie series stays within
     *  its containing chart's bounds while pulling out wedges,
     *  the pie series might shrink the total radius of the pie.</p>
     *       
     *  <p>If you dynamically pull out wedges at run time,
     *  the total pie radius shrinks.
     *  To avoid this effect, you can "reserve" the space
     *  and shrink the total radius initially by setting the
     *  <code>reserveExplodeRadius</code> property to the maximum explode radius
     *  you intend to set at run time.</p>
     *  
     *  <p>The value for an individual wedge in the
     *  <code>perWedgeExplodeRadius</code> Array is added to the value
     *  of the series's <code>explodeRadius</code> property to calculate
     *  the total explode radius for any given wedge.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get perWedgeExplodeRadius():Array /* of Number */
    {
        return _perWedgeExplodeRadius.concat();
    }

    /**
     *  @private
     */
    public function set perWedgeExplodeRadius(value:Array /* of Number */):void
    {
        _perWedgeExplodeRadius = value.concat();
        
        var n:int = _perWedgeExplodeRadius.length;
        for (var i:int = 0; i < n; i++) 
        {
            _perWedgeExplodeRadius[i] =
                _perWedgeExplodeRadius[i] is Number ?
                Math.max(0, Math.min(_perWedgeExplodeRadius[i], 1)) :
                undefined;          
        }

        invalidateData();
    }

    //----------------------------------
    //  renderDataType
    //----------------------------------

    [Inspectable(environment="none")]

    /**
     *  The subtype of ChartRenderData used by this series
     *  to store all data necessary to render.
     *  Subclasses can override and return a more specialized class
     *  if they need to store additional information for rendering.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function get renderDataType():Class
    {
        return PieSeriesRenderData;
    }
    
    //----------------------------------
    //  reserveExplodeRadius
    //----------------------------------

    /**
     *  @private
     *  Storage for the reserveExplodeRadius property.
     */
    private var _reserveExplodeRadius:Number = 0;
    
    [Inspectable(category="General")]

    /**
     *  A number from 0 to 1, specifying how much of the total radius
     *  of the pie series should be reserved to explode wedges at runtime.
     *  When a pie wedge is exploded, the series must shrink
     *  the total radius of the pie to make sure it doesn't exceed
     *  the bounds of its containing chart.
     *  Thus if a developer changes the explode value of a wedge at runtime,
     *  it can effectively shrink all the wedges rather than
     *  the desired effect of pulling out a single wedge.
     *  To avoid this, set <code>reserveExplodeRadius</code>
     *  to the maximum value you intend to explode any wedge at runtime.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get reserveExplodeRadius():Number
    {
        return _reserveExplodeRadius;
    }

    /**
     *  @private
     */
    public function set reserveExplodeRadius(value:Number):void
    {
        _reserveExplodeRadius =
            Math.max(0, Math.min(value, 1));
        
        invalidateData();
    }

    //----------------------------------
    //  startAngle
    //----------------------------------

    /**
     *  @private
     *  Storage for the startAngle property.
     */
    private var _startAngleRadians:Number = 0;
    
    [Inspectable(category="General")]

    /**
     *  Specifies the starting angle for the first slice of the PieChart control.
     *  The default value is 0,
     *  which is horizontal on the right side of the PieChart control.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get startAngle():Number
    {
        return _startAngleRadians * 180 / Math.PI;
    }

    /**
     *  @private
     */
    public function set startAngle(value:Number):void
    {
        const twopi:Number = 2 * Math.PI;
        _startAngleRadians = (value * Math.PI / 180) % twopi;
        if (value < 0)
            _startAngleRadians += twopi;
        
        invalidateTransform();
    }

 	//--------------------------------------------------------------------------
    //
    //  Overridden methods: UIComponent
    //
    //--------------------------------------------------------------------------

	
	/**
	 *  @private
	 */
	private function initStyles():Boolean
	{
		HaloDefaults.init(styleManager);
		
		var pieFills:Array /* of IFill */ = [];
		
		var n:int = HaloDefaults.defaultFills.length;
		for (var i:int = 0; i < n; i++)
		{
			pieFills[i] = HaloDefaults.defaultFills[i];
		}
		
		var pieSeriesStyle:CSSStyleDeclaration = styleManager.getStyleDeclaration("mx.charts.series.PieSeries");
		pieSeriesStyle.setStyle("itemRenderer", new ClassFactory(mx.charts.renderers.WedgeItemRenderer));
		pieSeriesStyle.setStyle("fills", pieFills);
		pieSeriesStyle.setStyle("legendMarkerRenderer", new ClassFactory(PieSeriesLegendMarker));
		pieSeriesStyle.setStyle("calloutStroke", new SolidColorStroke(0,0,1));
		
		return true;
	}
	
	/**
	 *  @inheritDoc
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	override public function set moduleFactory(factory:IFlexModuleFactory):void
	{
		super.moduleFactory = factory;
		
		if (_moduleFactoryInitialized)
			return;
		
		_moduleFactoryInitialized = true;
		
		// our style settings
		initStyles();
	}
	
    /**
	 *  @inheritDoc
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	override protected function commitProperties():void
	{
		super.commitProperties();
		
		var textFieldFactory:Class = getStyle('textFieldClass');
		if(textFieldFactory != null)
			_labelCache.factory = new ContextualClassFactory(textFieldFactory, moduleFactory);
		else
			_labelCache.factory = new ContextualClassFactory(UITextField, moduleFactory);
		
		if (dataTransform)
		{
			if (_angularAxis)
				dataTransform.setAxis(PolarTransform.ANGULAR_AXIS, _angularAxis);
		}
		
		var c:PolarChart = PolarChart(chart);
		if (c)
		{
			if (!_angularAxis && dataTransform.axes[PolarTransform.ANGULAR_AXIS] != c.angularAxis)
				PolarTransform(dataTransform).setAxis(PolarTransform.ANGULAR_AXIS, c.angularAxis);
		}
		
        dataTransform.elements = [this];
	}
	
    /**
	 *  @inheritDoc
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
    override protected function createChildren():void
    {
        super.createChildren();
        _measuringField = IUITextField(createInFontContext(UITextField));
        _measuringField.visible = false;
        _measuringField.styleName = this;
		
        _measuringField.autoSize = TextFieldAutoSize.LEFT;
        addChild(DisplayObject(_measuringField));
        
    }
    /**
     *  @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);

        var dataTransform:PolarTransform = PolarTransform(dataTransform);

        var g:Graphics = graphics;
        g.clear();
        _labelLayer.graphics.clear();

        if (!dataProvider)
        {
        	_labelCache.discard = true;
    		_labelCache.remove = true;
            _labelCache.count = 0;
            _labelCache.discard = false;
    		_labelCache.remove = false;
            return;
        }

        var i:int;
        var renderData:PieSeriesRenderData = (transitionRenderData)? PieSeriesRenderData(transitionRenderData) :_renderData;

        if (!renderData || !(renderData.filteredCache))
            return;
        var renderCache:Array /* of PieSeriesItem */ = renderData.filteredCache;
        var n:int = renderCache.length;
        var item:PieSeriesItem;



        // ff we have element bounds, go through and reverse map from the bounds to actual inner,outer radius and start/end angles.
        if (renderData == transitionRenderData && transitionRenderData.elementBounds)
        {
            const twoP:Number = Math.PI*2;
            var boundsArray:Array /* of Rectangle */ = transitionRenderData.elementBounds;
            n = renderCache.length;

            var visibleBoundsStart:Number = transitionRenderData.visibleRegion.left;
            var visibleBoundsEnd:Number = transitionRenderData.visibleRegion.right;

            for (i = 0; i < n; i++)
            {
                var dataItem:PieSeriesItem = renderCache[i];
                var dataItemBounds:Rectangle = boundsArray[i];
                var tmpStartAngle:Number = dataItemBounds.left;
                var tmpA:Number = dataItemBounds.width;

                if (tmpStartAngle < visibleBoundsStart)
                {
                    tmpA = Math.max(0,tmpA + tmpStartAngle - visibleBoundsStart);
                    tmpStartAngle = visibleBoundsStart;
                }
                else if (tmpStartAngle + tmpA > visibleBoundsEnd)
                {
                    tmpStartAngle= Math.min(visibleBoundsEnd,tmpStartAngle);
                    tmpA = Math.max(0,visibleBoundsEnd - tmpStartAngle);
                }

                dataItem.startAngle = tmpStartAngle;
                dataItem.angle = tmpA;
                dataItem.innerRadius = dataItemBounds.top;
                dataItem.outerRadius = dataItemBounds.bottom;
            }
        }

        if (renderData.itemSum == 0)
        {
        	_labelCache.discard = true;
    		_labelCache.remove = true;
            _labelCache.count = 0;
            _labelCache.discard = false;
    		_labelCache.remove = false;
            _instanceCache.count = 0;
            if (chart && (chart.showAllDataTips || chart.dataTipItemsSet))
        		chart.updateAllDataTips();
            return;
        }

        var labelPosition:String = getStyle("labelPosition");
        
        // secondly, if our inner radius is larger than our outer radius, we should be completely invisible, so we'll just hide the labels.
        if (getStyle("innerRadius") >= _outerRadius || (renderData == transitionRenderData) || n == 0){
        	_labelCache.discard = true;
    		_labelCache.remove = true;
            _labelCache.count = 0;
            _labelCache.discard = false;
    		_labelCache.remove = false;
        }
        else if (labelPosition=="outside")
            renderRadialLabels(renderData,renderCache);
        else if (labelPosition=="inside" || labelPosition=="insideWithCallout")
            renderInternalLabels(renderData,renderCache);
        else if (labelPosition=="callout")
            renderCalloutLabels(renderData);
        else{
           	_labelCache.discard = true;
    		_labelCache.remove = true;
            _labelCache.count = 0;
            _labelCache.discard = false;
    		_labelCache.remove = false;
        }




        // OK, draw away
        var fills:Array /* of IFill */ = getStyle("fills");
        var fillCount:int = fills.length;
        var inst:IFlexDisplayObject;
        var instances:Array /* of IFlexDisplayObject */;
        
		_instanceCache.factory = getStyle("itemRenderer");
        _instanceCache.count = n;
        instances = _instanceCache.instances;
		
        for (i = 0; i < n; i++)
        {
            item = renderCache[i];
            inst = instances[i];
            item.fill = fillFunction(item,i);
            if (!(item.fill))
            	item.fill = defaultFillFunction(item,i);           
            inst.setActualSize(unscaledWidth,unscaledHeight);
            item.itemRenderer = inst;
            (inst as IDataRenderer).data = item;
            if ((item.itemRenderer as Object).hasOwnProperty('invalidateDisplayList'))
            	(item.itemRenderer as Object).invalidateDisplayList();
        }
        if (allSeriesTransform && chart && chart.chartState == 0)
        	chart.updateAllDataTips();

    }

    /**
     *  @private
     */
    override public function styleChanged(styleProp:String):void
    {
        super.styleChanged(styleProp);

        if (styleProp == null || styleProp == "" || styleProp == "fills")
        {
            invalidateDisplayList();
            legendDataChanged();
        }
        if (styleProp == "labelPosition")
        {
            invalidateTransform();
        }
        if (styleProp == "renderDirection")
        {
            invalidateTransform();
        }
        if (styleProp == "itemRenderer")
        {
        	_instanceCache.remove = true;
            _instanceCache.discard = true;
        	_instanceCache.count = 0;
        	_instanceCache.discard = false;
        	_instanceCache.remove = false;
        }
    }

    /**
     *  @private
     *  since the labels aren't a decendant of the PieSeries, changes aren't propogating down.
     *  They should, since the PieSeries is the styleName of the labelLayer, but there seems 
     *  to be a bug. It's been logged, but I'm fixing it specifically in PieSeries for now
     *  by explciitly passing change notifications to the label layer.
     */
    override public function notifyStyleChangeInChildren(
                        styleProp:String, recursive:Boolean):void
    {
        super.notifyStyleChangeInChildren(styleProp,recursive);
        
        _labelLayer.styleChanged(styleProp);
        if (recursive)
        {
            _labelLayer.notifyStyleChangeInChildren(styleProp, recursive);
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods: ChartElement
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    override public function getAllDataPoints():Array /* of HitData */
    {
    	if (!_renderData)
    		return [];
    	if (!(_renderData.filteredCache))
    		return [];
    	if (_renderData.filteredCache.length == 0)
    		return[];
    	var itemArr:Array /* of PieSeriesItem */ = [];
    	if (chart && chart.dataTipItemsSet && dataTipItems)
    		itemArr = dataTipItems;
    	else if (chart && chart.showAllDataTips && _renderData.filteredCache)
    		itemArr = _renderData.filteredCache;
    	else
    		itemArr = [];
    	
    	var n:uint = itemArr.length;
    	var i:uint;
    	var inr:Number= _innerRadiusInPixelsScaledForExplode;
    	var startAngle:Number;
    	var angleAdjustment:Number;
        var renderDirection:String = getStyle('renderDirection');
        if(renderDirection == "clockwise")
        	angleAdjustment = _renderData.filteredCache[n - 1].startAngle;
        else
        	angleAdjustment = _renderData.filteredCache[0].startAngle;
    	var result:Array /* of HitData */ = [];
    	
    	for (i = 0; i < n; i++)
        {
            var v:PieSeriesItem = itemArr[i];
            if (_renderData.filteredCache.indexOf(v) == -1)
            {
            	var itemExists:Boolean = false;
            	var m:int  = _renderData.filteredCache.length;
            	for (var j:int = 0; j < m; j++)
            	{
            		if (v.item == _renderData.filteredCache[j].item)
            		{	
            			v = _renderData.filteredCache[j];
            			itemExists = true;
            			break;
            		}
            	}
            	if (!itemExists)
            		continue;
            }
            if (v)
        	{
            	startAngle = v.startAngle - angleAdjustment;

            	var a:Number;
            	if(renderDirection == "clockwise")
            		a  = startAngle + (2*Math.PI)-_startAngleRadians + v.angle/2;
            	else
            		a  = startAngle + _startAngleRadians + v.angle/2;
            	var xpos:Number = v.origin.x + Math.cos(a)*(inr + (_radiusInPixelsScaledForExplode-inr)*.5);
            	var ypos:Number = v.origin.y - Math.sin(a)*(inr + (_radiusInPixelsScaledForExplode-inr)*.5);;
            	var hd:HitData = new HitData(createDataID(v.index),0,xpos,ypos,v);
            	hd.dataTipFunction = formatDataTip;
            	var fills:Array /* of IFill */ = getStyle("fills");
            	if (fills)
            	{
                	// can't be strongly typed since it could be a raw string.
                	var fill:Object = PieSeriesItem(hd.chartItem).fill;
                	hd.contextColor = GraphicsUtilities.colorFromFill(fill);
            	}
            	result.push(hd);
        	}
        }
        return result;
    }

    /**
     *  @private
     */
    override public function findDataPoints(x:Number,y:Number,sensitivity:Number):Array /* of HitData */
    {
        // esg, 8/7/06: if your mouse is over a series when it gets added and displayed for the first time, this can get called
        // before updateData, and before and render data is constructed. The right long term fix is to make sure a stubbed out 
        // render data is _always_ present, but that's a little disruptive right now.
        if (interactive == false || !_renderData)
            return [];

        var dataTransform:PolarTransform = PolarTransform(dataTransform);
        var n:int = _renderData.filteredCache.length;
        var o:Point = dataTransform.origin;
        var hitItem:PieSeriesItem;
        var msPt:Point = new Point(x,y);
        var innerRadius:Number = getStyle("innerRadius");
        var inr:Number= _innerRadiusInPixelsScaledForExplode;
        var mp2:Number = Math.PI*2;

        if (n == 0)
            return [];

        var angle:Number = calcAngle(msPt.x - _origin.x,msPt.y - _origin.y);

        var angleAdjustment:Number;
        var renderDirection:String = getStyle('renderDirection');
        if(renderDirection == "clockwise")
        	angleAdjustment = _renderData.filteredCache[n - 1].startAngle;
        else
        	angleAdjustment = _renderData.filteredCache[0].startAngle;
       
        angle -= angleAdjustment;
        if (angle < 0)
            angle += mp2;

        var startAngle:Number;

        for (var i:int = 0; i < n; i++)
        {
            var v:PieSeriesItem = _renderData.filteredCache[i];

            angle = calcAngle(msPt.x - v.origin.x, msPt.y - v.origin.y);
            angle -= angleAdjustment;
            if (angle < 0)
                angle += mp2;
            if(renderDirection == "clockwise")
            {
				while(angle > mp2)
					angle = angle - mp2;
			}
            startAngle = v.startAngle - angleAdjustment;
            if (angle >= startAngle && angle < startAngle + v.angle)
            {
                hitItem = v;
                break;
            }
        }
        if (hitItem)
        {

            var dst2:Number = (x - hitItem.origin.x)*(x-hitItem.origin.x) + (y-hitItem.origin.y)*(y-hitItem.origin.y);
            var s2:Number = sensitivity * sensitivity;

            if (dst2 < inr*inr - s2 || dst2 > _radiusInPixelsScaledForExplode*_radiusInPixelsScaledForExplode + s2)
            {
                return [];
            }

            var a:Number;
            if(renderDirection == "clockwise")
            	a  = startAngle + (2*Math.PI)-_startAngleRadians + hitItem.angle/2;
            else
            	a  = startAngle + _startAngleRadians + hitItem.angle/2;
            var xpos:Number = hitItem.origin.x + Math.cos(a)*(inr + (_radiusInPixelsScaledForExplode-inr)*.5);
            var ypos:Number = hitItem.origin.y - Math.sin(a)*(inr + (_radiusInPixelsScaledForExplode-inr)*.5);;
            var hd:HitData = new HitData(createDataID(hitItem.index),0,xpos,ypos,hitItem);
            hd.dataTipFunction = formatDataTip;
            var fills:Array /* of IFill */ = getStyle("fills");
            if (fills)
            {
                // can't be strongly typed since it could be a raw string.
                var fill:Object = PieSeriesItem(hd.chartItem).fill;
                hd.contextColor = GraphicsUtilities.colorFromFill(fill);
            }
            return [hd];
        }
        return [];
    }
    
   /**
     *  @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;
        
        var dataTransform:PolarTransform = PolarTransform(dataTransform);
        
        if (n > 0)
        {
            data["d0"] = dataValues[0];
            
            dataTransform.getAxis(PolarTransform.ANGULAR_AXIS).
                mapCache(da, "d0", "v0");
        }
        
        if (n > 1)
        {
            data["d1"] = dataValues[1];
            
            dataTransform.getAxis(PolarTransform.RADIAL_AXIS).
                mapCache(da, "d1", "v1");           
        }
        
        dataTransform.transformCache(da, "v0", "s0", "v1", "s1");
        
        return new Point(dataTransform.origin.x +
                         Math.cos(data.s0) * data.s1,
                         dataTransform.origin.y -
                         Math.sin(data.s0) * data.s1);
    }
    
    /**
     *  @inheritDoc 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function localToData(v:Point):Array /* of Object */
    {
     	var dataTransform:PolarTransform = PolarTransform(dataTransform);
     	
        var dx:Number = v.x - dataTransform.origin.x;
        var dy:Number = v.y - dataTransform.origin.y;
        
        var a:Number = calcAngle(dx,dy);
        
        var r:Number = Math.sqrt(dx * dx + dy * dy);        
        
        var values:Array /* of Object */ = dataTransform.invertTransform(a, r);
        return values;
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods: Series
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override protected function updateData():void
    {
        var renderDataType:Class = this.renderDataType;
        _renderData= new renderDataType();

        _renderData.cache = [];

        if (dataProvider)
        {
            cursor.seek(CursorBookmark.FIRST);
            var i:int = 0;
            var itemClass:Class = itemType;
            while (!cursor.afterLast)
            {
                _renderData.cache[i]=new itemClass(this,cursor.current,i);
                i++;
                cursor.moveNext();
            }

            cacheDefaultValues(_field,_renderData.cache,"value");
        }

        super.updateData();
    }

    /**
     *  @private
     */
    override protected function updateMapping():void
    {

        dataTransform.getAxis(PolarTransform.ANGULAR_AXIS).mapCache(_renderData.cache,"value","number");

        var total:Number = 0;
        var n:int = _renderData.cache.length;
        var i:int;

        for (i = 0; i < n; i++)
        {
            var v:Number= _renderData.cache[i].number;
            if (!isNaN(v))
                total += _renderData.cache[i].number;
        }
        _renderData.itemSum = total;
        total /= 100;
        
        // avoid dividing by zero.  If total is 0, we're not going to be rendering anything anyway.
        // avoiding a divide by zero means we'll still end up with valid calculations, in case
        // we need to animate from or to these values.
        if (total == 0)
            total = 1;
        
        for (i = 0; i < n; i++)
        {
            _renderData.cache[i].percentValue = _renderData.cache[i].number/total;
        }

        super.updateMapping();
    }

    /**
     *  @private
     */
    override protected function updateFilter():void
    {
        _renderData.filteredCache = filterFunction(_renderData.cache);
        legendDataChanged();

        super.updateFilter();
    }

    /**
     *  @private
     */
    override protected function updateTransform():void
    {
        var dataTransform:PolarTransform = PolarTransform(dataTransform);

        dataTransform.transformCache(_renderData.filteredCache,"percentValue","angle",null,null);
        var sampleCount:int = _renderData.filteredCache.length;
        var lastItem:PieSeriesItem;
        var thisItem:PieSeriesItem;
        var i:int;

        if (sampleCount)
        {
            lastItem = _renderData.filteredCache[0];

            lastItem.startAngle = _startAngleRadians;
            for (i = 1; i < sampleCount; i++)
            {
                thisItem = _renderData.filteredCache[i];
                thisItem.startAngle = lastItem.startAngle + lastItem.angle;
                lastItem = thisItem;
            }
        }

        var renderData:PieSeriesRenderData = _renderData;

        var assignRadiusToAllItems:Boolean = true;
        var assignOriginToAllItems:Boolean = true;

        if (!renderData || !(renderData.filteredCache))
            return;
        var renderCache:Array /* of PieSeriesItem */ = renderData.filteredCache;

        //clean up before we start

        var n:int = renderCache.length;

        if (n == 0)
        {
            _labelCache.discard = true;
    		_labelCache.remove = true;
            _labelCache.count = 0;
            _labelCache.discard = false;
    		_labelCache.remove = false;
            return;
        }

        _origin = dataTransform.origin;

        var labelData:Object;

        // we're about to go through and calculate placement for our labels. That will determine how much we need to scale
        // our radius by. We'll start by being optomistic, and assuming that we need to scale everything by only whatever the preset values are.
        _radiusInPixelsAfterLabels = dataTransform.radius - DROP_SHADOW_SIZE;


        // if someone has asked to have any of our wedges exploded, we'll have to calculate their origin offsets so they get/
        // rendered in the correct place. We'll also have to check to see if we need to scale down the radius as a result. Remember
        // that at this point, all of our wedges are as big as they can be and still fit inside the labels. So by definition, if we want
        // one of them to stick out past the others, it will be sticking into the labels. To make up for this, we instead scale down all the
        // wedges as much as we need to to make sure that the wedges sticking out just reach the edge.
        if (_perWedgeExplodeRadius && _perWedgeExplodeRadius.length > 0)
        {
            // determine per-wedge offsets

            var er:Number=_explodeRadius;
            if (isNaN(er))
                er = 0;
            _maxExplodeRadiusRatio = er;


            // This code always reduces the radius to match the maximum explode size.
            // However, if that maximum explode size is on an angle at 45 degrees,
            // it might not extend past the bounds using the current radius.
            // Or, it might extend vertically, but there may be room to spare vertically.
            // So we should really track maximum width/height of the bounds of all wedges,
            // and adjust the outer radius appropriately.

            for (i = 0; i < n; i++)
            {
                var pwer:Number = (_perWedgeExplodeRadius[i] != null)? (parseFloat(_perWedgeExplodeRadius[i])):0;
                if (isNaN(pwer))
                    pwer = 0;
                _maxExplodeRadiusRatio = Math.max(_maxExplodeRadiusRatio,er + pwer);
            }
        }
        else if (!isNaN(_explodeRadius) && _explodeRadius != 0)
        {
            _maxExplodeRadiusRatio = _explodeRadius;
        }
        else
        {
            // no exploding wedges
            _maxExplodeRadiusRatio = 0;
        }
        _maxExplodeRadiusRatio = Math.max(_maxExplodeRadiusRatio,_reserveExplodeRadius);



        _renderData.labelScale = 1;

        var labelPosition:String = getStyle("labelPosition");

        // now we caluclate label positioning. There's a couple of special cases we needt ohandle first. FIrst of all, we
        // know the player can't handle fonts scaled to below two pts.  so if our scale would make our labels smaller than 2 pts, we just won't
        // show the pts.

        var r1:Point = new Point(0,0);
        var r2:Point = new Point(1,1);
        r1 = localToGlobal(r1);
        r2 = localToGlobal(r2);

        // the flash player screws up display of device fonts at very small point sizes...it blows out to large sizes instead. So if it looks
        // like fonts will be displayed at a tiny point size, just don't show them at all.
        var actualScale:Number;
		if(chart && chart.layoutDirection == LayoutDirection.RTL)
			actualScale = r1.x - r2.x;
		else
			actualScale = r2.x - r1.x;
        if (getStyle("fontSize")*actualScale < 2)
            labelPosition = "none";
        
    
        // secondly, if our inner radius is larger than our outer radius, we should be completely invisible, so we'll just hide the labels.
        if (getStyle("innerRadius") >= _outerRadius)
        {
            _renderData.labelData = null;
            _labelCache.discard = true;
    		_labelCache.remove = true;
            _labelCache.count = 0;
            _labelCache.discard = false;
    		_labelCache.remove = false;
        }
        else if (labelPosition=="outside")
            _renderData.labelData = measureRadialLabels(renderCache);
        else if (labelPosition=="callout")
            _renderData.labelData = measureCalloutLabels(renderCache);
        else if (labelPosition=="inside")
            _renderData.labelData = measureInternalLabels(false,renderCache);
        else if (labelPosition=="insideWithCallout")
            _renderData.labelData = measureInternalLabels(true,renderCache);
        else
        {
            _renderData.labelData = null;
            _labelCache.discard = true;
    		_labelCache.remove = true;
            _labelCache.count = 0;
            _labelCache.discard = false;
    		_labelCache.remove = false;
        }

        // OK, at this point, we've calculated our actual label placement, and thus can calculate our actual
        // radius' (radii?).  The outer radius is whatever outerradius is provided by our enclosing chart, scaled as appropriate to
        // make room for the labels.
        // the inner radius, on the other hand, is the value specified by the devloper (through styles) scaled as appropriate to
        // make room for the labels.
        var innerRadius:Number = getStyle("innerRadius");
        var item:PieSeriesItem;

        _innerRadiusInPixels = _radiusInPixelsAfterLabels*innerRadius;
        _radiusInPixelsAfterLabels = _radiusInPixelsAfterLabels*_outerRadius;
        // ok, we know how much we need to reduce the radius by. let's do it.
        _radiusInPixelsScaledForExplode = _radiusInPixelsAfterLabels*(1-_maxExplodeRadiusRatio);
        _innerRadiusInPixelsScaledForExplode = _innerRadiusInPixels*(1-_maxExplodeRadiusRatio);

        var renderDirection:String = getStyle('renderDirection');
        // if someone has asked to have any of our wedges exploded, we'll have to calculate their origin offsets so they get/
        // rendered in the correct place. We'll also have to check to see if we need to scale down the radius as a result. Remember
        // that at this point, all of our wedges are as big as they can be and still fit inside the labels. So by definition, if we want
        // one of them to stick out past the others, it will be sticking into the labels. To make up for this, we instead scale down all the
        // wedges as much as we need to to make sure that the wedges sticking out just reach the edge.
        if ((_perWedgeExplodeRadius && _perWedgeExplodeRadius.length > 0) || _explodeRadius != 0)
        {
            assignOriginToAllItems= false;

            // determine per-wedge offsets

            var sa:Number = startAngle;


            // This code always reduces the radius to match the maximum explode size.
            // However, if that maximum explode size is on an angle at 45 degrees,
            // it might not extend past the bounds using the current radius.
            // Or, it might extend vertically, but there may be room to spare vertically.
            // So we should really track maximum width/height of the bounds of all wedges,
            // and adjust the outer radius appropriately.

            for (i = 0; i < n; i++)
            {
                var wedgeExplodeRadius:Number = calculateExplodeRadiusForWedge(i);

                item = renderCache[i];
                if (!isNaN(item.startAngle))
                    sa = item.startAngle;
                var wedgeAngle:Number = renderCache[i].angle;

                var angle:Number;
                if(renderDirection == 'clockwise')
                	angle = (2*Math.PI)-(wedgeAngle/2 + sa);
                else
                	angle = wedgeAngle/2 + sa;

                item.origin = new Point(_origin.x + Math.cos(angle)*wedgeExplodeRadius,_origin.y + -Math.sin(angle)*wedgeExplodeRadius);

                sa += wedgeAngle;
            }
        }

        if (assignOriginToAllItems || assignRadiusToAllItems)
        {
            for (i = 0; i< sampleCount; i++)
            {
                item = renderCache[i];
                if (assignOriginToAllItems)
                    item.origin = _origin;

                if (assignRadiusToAllItems)
                {
                    item.innerRadius = _innerRadiusInPixelsScaledForExplode;
                    item.outerRadius = _radiusInPixelsScaledForExplode;
                }
            }
        }
        
        if(renderDirection == "clockwise")
        {
			if (sampleCount)
        	{
           		for (i = 0; i < sampleCount; i++)
            	{
                	item = _renderData.filteredCache[i];
                	item.startAngle = 2*Math.PI - (item.startAngle + item.angle);
            	}
        	}
        }
        super.updateTransform();
    }

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function getElementBounds(renderData:Object):void
    {
        var rb :Array /* of Rectangle */ = [];
        var cache:Array /* of PieSeriesItem */ = renderData.filteredCache;
        var sampleCount:int = cache.length;
        var sample:PieSeriesItem;

        var minAngle:Number = Number.MAX_VALUE;
        var maxAngle:Number = Number.MIN_VALUE;
        for (var i:int = 0; i < sampleCount; i++)
        {
            sample = cache[i];
            rb.push(new Rectangle(sample.startAngle,sample.innerRadius,sample.angle,sample.outerRadius - sample.innerRadius));
            maxAngle = Math.max(maxAngle,sample.startAngle + sample.angle);
            minAngle = Math.min(minAngle,sample.startAngle);
        }


        sample  = cache[sampleCount-1];
        renderData.elementBounds = rb;
        renderData.bounds =     new Rectangle(minAngle,_innerRadiusInPixelsScaledForExplode,maxAngle - minAngle,_radiusInPixelsScaledForExplode - _innerRadiusInPixelsScaledForExplode);
        renderData.visibleRegion =  new Rectangle(minAngle,_innerRadiusInPixelsScaledForExplode,maxAngle - minAngle,_radiusInPixelsScaledForExplode - _innerRadiusInPixelsScaledForExplode);
    }

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function beginInterpolation(sourceRenderData:Object,destRenderData:Object):Object
    {
        var idata:Object = initializeInterpolationData(
            sourceRenderData.filteredCache, destRenderData.filteredCache,
            { angle: true, startAngle: true,
              innerRadius: true, outerRadius: true }, itemType,
            { lastInvalidSrcValue: 0, lastInvalidSrcIndex: NaN,
              lastInvalidDestValue: 0, lastInvalidDestIndex: NaN });

        var origin:Point = PolarTransform(dataTransform).origin;
        
        var srcCache:Array /* of PieSeriesItem */ = sourceRenderData.filteredCache;
        var dstCache:Array /* of PieSeriesItem */ = destRenderData.filteredCache;
        
        var n:int = Math.max(srcCache.length,dstCache.length);
        var shortLen:int = Math.min(srcCache.length,dstCache.length);
        
        var srcItem:PieSeriesItem;
        var dstItem:PieSeriesItem;

        for (var i:int = 0; i < shortLen; i++)
        {
            dstItem = dstCache[i];
            srcItem = srcCache[i];
            idata.interpolationSource[i].origin = srcItem.origin;
            idata.deltaCache[i].origin = new Point(dstItem.origin.x - srcItem.origin.x,
                                                   dstItem.origin.y - srcItem.origin.y);
            idata.cache[i].origin = srcItem.origin.clone();
        }

        if (shortLen < srcCache.length)
        {
            // src is longer than dest
            for (i = shortLen; i < n; i++)
            {
                srcItem = srcCache[i];
                idata.interpolationSource[i].origin = srcItem.origin;
                idata.deltaCache[i].origin = new Point(origin.x - srcItem.origin.x,
                                                       origin.y - srcItem.origin.y);
                idata.cache[i].origin = srcItem.origin.clone();
            }
        }
        else
        {
            // dest is longer than src
            for (i = shortLen; i < n; i++)
            {
                dstItem = dstCache[i];
                idata.interpolationSource[i].origin = origin;
                idata.deltaCache[i].origin = new Point(dstItem.origin.x - origin.x,
                                                       dstItem.origin.y - origin.y);
                idata.cache[i].origin = origin.clone();
            }
        }
        var interpolationRenderData:Object = destRenderData.clone();

        interpolationRenderData.cache = idata.cache;
        interpolationRenderData.filteredCache = idata.cache;

        transitionRenderData = interpolationRenderData;
        return idata;
    }

    /**
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override public function interpolate(interpolationValues:Array /* of Number */,interpolationData:Object):void
    {
        super.interpolate(interpolationValues,interpolationData);

        var n:int = interpolationValues.length;
        var srcCache:Array /* of PieSeriesItem */ = interpolationData.interpolationSource;
        var deltaCache:Array /* of PieSeriesItem */ = interpolationData.deltaCache;
        var interpolationCache:Array /* of PieSeriesItem */ = interpolationData.cache;
        var iProps:Object = interpolationData.properties;

        n = interpolationCache.length;


        for (var i:int = 0; i < n; i++)
        {
            var interpValue:Number=interpolationValues[i];
            var src:PieSeriesItem = srcCache[i];
            var delta:PieSeriesItem = deltaCache[i];
            var interp:PieSeriesItem = interpolationCache[i];

            interp.origin.x = src.origin.x + delta.origin.x * interpValue;
            interp.origin.y = src.origin.y + delta.origin.y * interpValue;
        }
    }

    /** 
     *  @inheritDoc
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override protected function getMissingInterpolationValues(sourceProps:Object,
    									srcCache:Array /* of PieSeriesItem */,destProps:Object,
    									destCache:Array /* of PieSeriesItem */,
    									index:Number,customData:Object):void
    {
        for (var p:String in sourceProps)
        {
            var src:Number = sourceProps[p];
            var dst:Number = destProps[p];
            var i:int;
			var n:int;
			
            if (p == "startAngle")
            {
                if (isNaN(src))
                {
                    if (customData.lastInvalidSrcIndex != index-1)
                    {
                        if (index == 0)
                        {
                        	n  = srcCache.length;
                            for (i = 0; i < n; i++)
                            {
                                if (srcCache[i] != null && !isNaN(srcCache[i].startAngle))
                                {
                                    customData.lastInvalidSrcValue = srcCache[i].startAngle + srcCache[i].angle;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            for (i = index - 1; i >= 0; i--)
                            {
                                if (srcCache[i] != null && !isNaN(srcCache[i].startAngle))
                                {
                                    customData.lastInvalidSrcValue = srcCache[i].startAngle + srcCache[i].angle;
                                    break;
                                }
                            }
                        }
                    }

                    src = customData.lastInvalidSrcValue;
                    customData.lastInvalidSrcIndex = index;
                }

                if (isNaN(dst))
                {
                    if (customData.lastInvalidDestIndex != index-1)
                    {
                        if (index == 0)
                        {
                        	n = destCache.length;
                            for (i = 0; i < n; i++)
                            {
                                if (destCache[i] != null && !isNaN(destCache[i].startAngle))
                                {
                                    customData.lastInvalidDestValue = destCache[i].startAngle + destCache[i].angle;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            for (i = index - 1; i >= 0; i--)
                            {
                                if (destCache[i] != null && !isNaN(destCache[i].startAngle))
                                {
                                    customData.lastInvalidDestValue = destCache[i].startAngle + destCache[i].angle;
                                    break;
                                }
                            }
                        }
                    }
                    dst = customData.lastInvalidDestValue;
                    customData.lastInvalidDestIndex = index;
                }
            }
            else if (p == "angle")
            {
                if (isNaN(src))
                    src = 0;
                if (isNaN(dst))
                    dst = 0;
            }
            else if (p == "innerRadius")
            {
                if (isNaN(src))
                {
                    src = _innerRadiusInPixelsScaledForExplode;
                }
                if (isNaN(dst))
                {
                    dst = _innerRadiusInPixelsScaledForExplode;
                }
            }
            else if (p == "outerRadius")
            {
                if (isNaN(src))
                {
                    src = _radiusInPixelsScaledForExplode;
                }
                if (isNaN(dst))
                {
                    dst = _radiusInPixelsScaledForExplode;
                }
            }
            else
            {
                if (isNaN(src))
                {
                    src = 0;
                }
                if (isNaN(dst))
                {
                    dst = 0;
                }
            }

            sourceProps[p] = src;
            destProps[p] = dst;
        }
    }

    /**
     *  @private
     */
    override public function getItemsInRegion(r:Rectangle):Array /* of PieSeriesItem */
    {
        // esg, 8/7/06: if your mouse is over a series when it gets added and displayed for the first time, this can get called
        // before updateData, and before and render data is constructed. The right long term fix is to make sure a stubbed out 
        // render data is _always_ present, but that's a little disruptive right now.
        if (interactive == false || !_renderData)
            return [];
        
        var arrItems:Array /* of PieSeriesItem */ = [];    
        var localRectangle:Rectangle = new Rectangle();
        var n:uint = _renderData.filteredCache.length;
        var inr:Number= _innerRadiusInPixelsScaledForExplode;
        
        localRectangle.topLeft = globalToLocal(r.topLeft);
        localRectangle.bottomRight = globalToLocal(r.bottomRight);
        var angleAdjustment:Number;
        var renderDirection:String = getStyle('renderDirection');
        if(renderDirection == "clockwise")
        	angleAdjustment = _renderData.filteredCache[n - 1].startAngle;
        else
        	angleAdjustment = _renderData.filteredCache[0].startAngle;
        for (var i:int = 0; i < n; i++)
        {
            var v:PieSeriesItem = _renderData.filteredCache[i];
            var startAngle:Number = v.startAngle - angleAdjustment;
            var a:Number;
             if(renderDirection == "clockwise")
             	a  = startAngle + (2*Math.PI)-_startAngleRadians + v.angle/2;
             else
             	a  = startAngle + _startAngleRadians + v.angle/2;
            var xpos:Number = v.origin.x + Math.cos(a)*(inr + (_radiusInPixelsScaledForExplode-inr)*.5);
            var ypos:Number = v.origin.y - Math.sin(a)*(inr + (_radiusInPixelsScaledForExplode-inr)*.5);;
            
            if (localRectangle.contains(xpos,ypos))
                arrItems.push(v);
        }
        return arrItems;
    }

    /**
     *  @private
     */
    override public function describeData(dimension:String,
                                          requiredFields:uint):Array /* of DataDescription */
    {
        validateData();

        if (dimension == PolarTransform.ANGULAR_AXIS)
        {
            var description:DataDescription = new DataDescription();
            description.boundedValues = null;
            description.min = 0;
            description.max = 100;
            return [ description ];
        }
        
        return [];
    }
    
     
    /**
     * @private
     */ 
    override protected function defaultFilterFunction(cache:Array /*of PieSeriesItem */ ):Array /*of PieSeriesItem*/
    {
    	var filteredCache:Array /*of PieSeriesItem*/ = [];	
    	if (filterDataValues == "outsideRange" || filterDataValues == "nulls")
        {
            filteredCache = cache.concat();
            stripNaNs(filteredCache,"number");
        }
       
        else if (filterDataValues == "none")
        {
            filteredCache = cache;
        }
        return filteredCache;
    }
    

	//--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
    
	/**
     *  @private
     */
    private function defaultFillFunction(element:PieSeriesItem,i:Number):IFill
    {
        _localFills = getStyle("fills");
        _fillCount = _localFills.length; 
        return(GraphicsUtilities.fillFromStyle(_localFills[i % _fillCount]));
    }

    /**
     *  @private
     */
    private function calculateExplodeRadiusForWedge(i:int):Number
    {
        var pwer:Number = (_perWedgeExplodeRadius && _perWedgeExplodeRadius[i] != null)? (parseFloat(_perWedgeExplodeRadius[i])):0;
        if (isNaN(pwer))
            pwer = 0;
        return (((isNaN(_explodeRadius))?0:_explodeRadius)+pwer) * _radiusInPixelsAfterLabels;
    }

    /**
     *  @private
     */
    private function measureCalloutLabels(renderCache:Array /* of PieSeriesItem */):Object //type return value
    {
        var n:int = renderCache.length;

        var baseAngle:Number = _startAngleRadians;

        for (var i:int = 0; i < n; i++)
        {
            var v:PieSeriesItem = renderCache[i];

            //type
            if (_labelFunction != null)
                _measuringField.text = v.labelText = _labelFunction(v.item,_field,i,v.percentValue);
            else if (_labelField)
                _measuringField.text = v.labelText = v.item[_labelField];
            else
                _measuringField.text = v.labelText = v.value.toString();

            var renderDirection:String = getStyle('renderDirection');
        	if(renderDirection == "clockwise")
        		v.labelAngle = (2*Math.PI - (baseAngle + v.angle/2))%(2*Math.PI);
        	else
        		v.labelAngle = (baseAngle + v.angle/2)%(2*Math.PI);
            v.labelCos = Math.cos(v.labelAngle);
            v.labelSin = -Math.sin(v.labelAngle);
            v.labelWidth = _measuringField.width;
            v.labelHeight = _measuringField.height;
            baseAngle += v.angle;
        }

        return auxMeasureCalloutLabels(_renderData,renderCache);
    }

    /**
     *  @private
     */
    private function auxMeasureCalloutLabels(renderData:PieSeriesRenderData,labelSet:Array /* of PieSeriesItem */):Object //type return value
    {
        var dataTransform:PolarTransform = PolarTransform(dataTransform);
        var n:int = labelSet.length;
        var rc:Rectangle= new Rectangle(0,0,unscaledWidth,unscaledHeight);

        var baseRadius:Number = dataTransform.radius*_outerRadius;

        var baseAngle:Number = _startAngleRadians;

        var leftStack:Array /* of PieSeriesItem */ = [];
        var rightStack:Array /* of PieSeriesItem */ = [];
        var leftHeight:Number = 0;
        var rightHeight:Number = 0;
        var i:int;
        var ly:Number;
        var labelScale:Number =  renderData.labelScale;

        //type
        var ld:PieSeriesItem;

        var lastTop:Number;

        var calloutGap:Number = getStyle("calloutGap");
        for (i = 0; i < n; i++)
        {

            //type
            ld = labelSet[i];
            if (((ld.labelAngle+Math.PI/2) % (2*Math.PI)) < Math.PI)
            {
                //right half
                if (ld.labelAngle > Math.PI)
                    ld.labelAngle -= 2*Math.PI

                ld.labelX = 0;
                baseRadius = Math.min(baseRadius,rc.right - ld.labelWidth - _origin.x - calloutGap);
                rightStack.push(ld);
                rightHeight += ld.labelHeight;

            }
            else
            {
                //left half
                ld.labelX = - ld.labelWidth;
                baseRadius = Math.min(baseRadius,_origin.x  - ld.labelWidth - calloutGap);
                leftStack.push(ld);
                leftHeight += ld.labelHeight;
            }

            ly = -ld.labelHeight/2 + _origin.y + ld.labelSin*baseRadius*1.1;
            if (ly < rc.top)
            {
                baseRadius = (rc.top + ld.labelHeight/2 - _origin.y)/(ld.labelSin*1.1);
            }
            if (ly > rc.bottom - ld.labelHeight)
            {
                baseRadius = (rc.bottom - ld.labelHeight+ ld.labelHeight/2 - _origin.y)/(ld.labelSin*1.1);
            }

        }


        leftStack.sortOn("labelAngle",Array.NUMERIC);
        rightStack.sortOn("labelAngle",Array.NUMERIC | Array.DESCENDING);

        if (leftHeight > rc.height)
            labelScale = Math.min(labelScale,rc.height / leftHeight);

        if (rightHeight > rc.height)
            labelScale = Math.min(labelScale,rc.height / rightHeight);

        if ((baseRadius) < (1-maxLabelRadius)*(dataTransform.radius*_outerRadius))
        {
            // we've scaled down too much. Let's adjust backwards.
            var oldTextSize:Number = dataTransform.radius*_outerRadius - baseRadius - calloutGap;
            baseRadius = (1-maxLabelRadius)*(dataTransform.radius*_outerRadius);
            labelScale = Math.min(labelScale,(dataTransform.radius*_outerRadius - baseRadius - calloutGap)/oldTextSize);
        }
        //now, convert our baseRadius base to a scaled down fullRadius
        _radiusInPixelsAfterLabels = baseRadius/_outerRadius;

        var leftEdge:Number = _origin.x - baseRadius - calloutGap;
        var lastBottom:Number = 0;
        
        n = leftStack.length;
        for (i = 0; i < n; i++)
        {
            //type
            ld = leftStack[i];

            ld.labelX = leftEdge + ld.labelX*labelScale;
            ly = -ld.labelHeight/2 + _origin.y + ld.labelSin*baseRadius*1.1;
            if (ly < lastBottom)
                ly = lastBottom;
            ld.labelY = ly;
            lastBottom = ly + ld.labelHeight;
        }
        if (lastBottom > rc.bottom)
        {
            lastTop = rc.bottom;
            for (i = n - 1; i >= 0; i--)
            {
                ld = leftStack[i];
                if (ld.labelY + ld.labelHeight <= lastTop)
                {
                    break;
                }
                ld.labelY = lastTop - ld.labelHeight;
                lastTop = ld.labelY;
            }
        }


        var rightEdge:Number = _origin.x + baseRadius + calloutGap;
        lastBottom = 0;

		n = rightStack.length;
        for (i = 0; i < n; i++)
        {
            ld = rightStack[i];


            ld.labelX = rightEdge;
            ly = -ld.labelHeight/2 + _origin.y + ld.labelSin*baseRadius*1.1;
            if (ly < lastBottom)
                ly = lastBottom;
            ld.labelY = ly;
            lastBottom = ly + ld.labelHeight;
        }
        if (lastBottom > rc.bottom)
        {
            lastTop = rc.bottom;
            for (i = n - 1; i >= 0; i--)
            {
                ld = rightStack[i];

                if (ld.labelY + ld.labelHeight <= lastTop)
                {
                    break;
                }
                ld.labelY = lastTop - ld.labelHeight;
                lastTop = ld.labelY;
            }
        }

        renderData.labelScale = labelScale;
        return { leftStack: leftStack, rightStack: rightStack };
    }

    /**
     *  @private
     */
    private function renderCalloutLabels(renderData:PieSeriesRenderData):void
    {
    	_labelCache.discard = true;
    	_labelCache.remove = true;
    	_labelCache.count = 0;
    	_labelCache.discard = false;
    	_labelCache.remove = false;
        var labelData:Object = renderData.labelData;
        if (!labelData)
            return;
        _labelCache.count = labelData.leftStack.length + labelData.rightStack.length;
        auxRenderCalloutLabels(renderData,renderData.labelData,0);
    }

    /**
     *  @private
     */
    private function auxRenderCalloutLabels(renderData:PieSeriesRenderData,labelData:Object,firstLabelIndex:Number):void
    {
        var dataTransform:PolarTransform = PolarTransform(dataTransform);
        var rc:Rectangle = new Rectangle(0, 0, unscaledWidth, unscaledHeight);
        var leftStack:Array /* of PieSeriesItem */ = labelData.leftStack;
        var rightStack:Array /* of PieSeriesItem */ = labelData.rightStack;

        var g:Graphics = _labelLayer.graphics;
        var labels:Array /* of IUITextField */ = _labelCache.instances;

        var inr:Number= _innerRadiusInPixelsScaledForExplode;
            var startRad:Number = inr + (_radiusInPixelsScaledForExplode-inr)*.8;

        var calloutStroke:IStroke = getStyle("calloutStroke");



        if (calloutStroke)
        {
            GraphicsUtilities.setLineStyle(g,calloutStroke);
        }

        var calloutGap:Number = getStyle("calloutGap");
        var i:int;
        var n:int;
        //type
        var ld:PieSeriesItem;
        var label:IUITextField;
        var endRad:Number;
        var labelScale:Number = renderData.labelScale;

        var leftEdge:Number = _origin.x - _radiusInPixelsAfterLabels - calloutGap;
        
        n = leftStack.length;
        for (i = 0;i < n; i++)
        {
            ld = leftStack[i];
            if (ld.percentValue == 0)
            	continue;
            var o:Point = ld.origin;


            label = ld.label = labels[firstLabelIndex++];
			label.scaleX = label.scaleY = labelScale;
            label.x = ld.labelX;
            label.y = ld.labelY;
            label.text = ld.labelText;
            

            endRad = (ld.labelSin == 0)? leftEdge:((label.y + label.height/2 - o.y)/ld.labelSin);

            if (endRad >= startRad)
            {
                if (o.y + ld.labelSin * endRad > rc.top)
            	{
            		g.moveTo(o.x + ld.labelCos*startRad,o.y + ld.labelSin*startRad);
                	if (o.x + ld.labelCos*endRad > leftEdge)
                	{
                    	g.lineTo(o.x + ld.labelCos*endRad,o.y + ld.labelSin*endRad);
                	}
                	else
                	{
                    	g.lineTo(o.x + ld.labelCos*_radiusInPixelsScaledForExplode,o.y + ld.labelSin*_radiusInPixelsScaledForExplode);
                	}
                	g.lineTo(leftEdge,o.y + ld.labelSin*endRad);
             	}
            }
            else if (endRad > _radiusInPixelsScaledForExplode*.2)
            {
                if (o.y + ld.labelSin * endRad > rc.top)
            	{
            		g.moveTo(o.x + ld.labelCos*endRad,o.y + ld.labelSin*endRad);
                	g.lineTo(leftEdge,o.y + ld.labelSin*endRad);
             	}
            }	
            else
            {
            	if (o.y + ld.labelSin * endRad > rc.top)
            	{
                	g.moveTo(o.x + ld.labelCos*_radiusInPixelsScaledForExplode*.2,o.y + ld.labelSin*_radiusInPixelsScaledForExplode*.2);
                	g.lineTo(leftEdge,o.y + ld.labelSin*endRad);
             	}
            }
        }
        var rightEdge:Number = _origin.x + _radiusInPixelsAfterLabels + calloutGap;
        
        n = rightStack.length;
        for (i = 0; i < n; i++)
        {
            ld = rightStack[i];
			if (ld.percentValue == 0)
				continue;
            o = ld.origin;


            label = ld.label = labels[firstLabelIndex++];
			label.scaleX = label.scaleY = labelScale;
            label.x = ld.labelX;
            label.y = ld.labelY;
            label.text = ld.labelText;
            

            endRad = (label.y + label.height/2 - o.y)/ld.labelSin;

            if (endRad == Infinity || endRad == -Infinity || isNaN(endRad))
            {
                // valid case: when ld.labelSin == 0, which occurs when the label's angle == 0
                if (o.y + ld.labelSin * endRad > rc.top)
            	{
                	g.moveTo(o.x + ld.labelCos*startRad,o.y + ld.labelSin*startRad);
                	g.lineTo(rightEdge,o.y + ld.labelSin*startRad);
             	}
            }
            else if (endRad >= startRad)
            {
             	if (o.y + ld.labelSin * endRad > rc.top)
            	{
            		g.moveTo(o.x + ld.labelCos*startRad,o.y + ld.labelSin*startRad);
                	if (o.x + ld.labelCos*endRad < rightEdge)
                	{
                    	g.lineTo(o.x + ld.labelCos*endRad,o.y + ld.labelSin*endRad);
                	}
                	else
                	{
                    	g.lineTo(o.x + ld.labelCos*_radiusInPixelsScaledForExplode,o.y + ld.labelSin*_radiusInPixelsScaledForExplode);
                	}
                	g.lineTo(rightEdge,o.y + ld.labelSin*endRad);
             	}
            }
            else if (endRad > _radiusInPixelsScaledForExplode*.2)
            {
            	if (o.y + ld.labelSin * endRad > rc.top)
            	{
                	g.moveTo(o.x + ld.labelCos*endRad,o.y + ld.labelSin*endRad);
                	g.lineTo(rightEdge,o.y + ld.labelSin*endRad);
             	}
            }
           else
            {
            	if (o.y + ld.labelSin * endRad > rc.top)
            	{
                	g.moveTo(o.x + ld.labelCos*_radiusInPixelsScaledForExplode*.2,o.y + ld.labelSin*_radiusInPixelsScaledForExplode*.2);
                	g.lineTo(rightEdge,o.y + ld.labelSin*endRad);
             	}
            }
        }
    }

    /**
     *  @private
     */
    private function renderRadialLabels(renderData:PieSeriesRenderData, renderCache:Array /* of PieSeriesItem */):void
    {
    	_labelCache.discard = true;
    	_labelCache.remove = true;
    	_labelCache.count = 0;
    	_labelCache.remove = false;
    	_labelCache.discard = false;
    	
        var n:int = renderCache.length;
        _labelCache.count = n;
        var labels:Array /* of IUITextField */ = _labelCache.instances;

        var i:int;
        var ld:PieSeriesItem;
        var label:IUITextField;
        var labelScale:Number = renderData.labelScale;

        for (i = 0; i < n; i++)
        {
            ld = renderCache[i];
            if (ld.percentValue == 0)
            	continue;
            label = labels[i];
            ld.label = label;
            label.text = ld.labelText;
            label.scaleX = label.scaleY = labelScale;
            label.x = ld.labelX;
            label.y = ld.labelY;
        }

    }

    /**
     *  @private
     */
    private function measureRadialLabels(renderCache:Array /* of PieSeriesItem */):Object
    {
        var dataTransform:PolarTransform = PolarTransform(dataTransform);
        var n:int = renderCache.length;
        var rc:Rectangle= new Rectangle(0,0,unscaledWidth,unscaledHeight);
        var o:Point = dataTransform.origin;

        var labels:Array /* of IUITextField */ = _labelCache.instances;


        var baseRadius:Number = dataTransform.radius*_outerRadius;

        var labelData:Array /* of PieSeriesItem */ = [];
        var baseAngle:Number = _startAngleRadians;
        var i:int;
        var ld:PieSeriesItem;
        var label:IUITextField;
        var labelScale:Number = 1;
        for (i = 0; i < n; i++)
        {
            ld = renderCache[i];

            if (_labelFunction != null)
                _measuringField.text = ld.labelText = _labelFunction(renderCache[i].item,_field,i,renderCache[i].percentValue);
            else if (_labelField)
                _measuringField.text = ld.labelText = renderCache[i].item[_labelField];
            else
                _measuringField.text = ld.labelText = renderCache[i].value.toString();
	
			var renderDirection:String = getStyle('renderDirection');
        	if(renderDirection == "clockwise")
        		ld.labelAngle = (2*Math.PI - (baseAngle + renderCache[i].angle/2))%(2*Math.PI);
        	else
        		ld.labelAngle = (baseAngle + renderCache[i].angle/2)%(2*Math.PI);
            
            ld.labelCos = Math.cos(ld.labelAngle);
            ld.labelSin = -Math.sin(ld.labelAngle);

            if (ld.labelAngle<Math.PI)
            {
                // top half
                ld.labelY = - _measuringField.height;
                baseRadius = Math.min(baseRadius,(o.y  - _measuringField.height)/Math.abs(ld.labelSin));
            }
            else
            {
                //bottom half;
                ld.labelY  = 0;
                baseRadius = Math.min(baseRadius,(rc.bottom - _measuringField.height - o.y)/Math.abs(ld.labelSin));
            }
            if (((ld.labelAngle+Math.PI/2) % (2*Math.PI)) < Math.PI)
            {
                //right half
                ld.labelX = 0;
                baseRadius = Math.min(baseRadius,(rc.right - _measuringField.width - o.x)/Math.abs(ld.labelCos));
            }
            else
            {
                //left half
                ld.labelX = - _measuringField.width;
                baseRadius = Math.min(baseRadius,(o.x  - _measuringField.width)/Math.abs(ld.labelCos));
            }
            labelData[i] = ld;
            baseAngle += renderCache[i].angle;
        }



        if ((baseRadius) < (1-maxLabelRadius)*(dataTransform.radius*_outerRadius))
        {
            // we've scaled down too much. Let's adjust backwards.
            var oldTextSize:Number = dataTransform.radius*_outerRadius - baseRadius;
            baseRadius = (1-maxLabelRadius)*(dataTransform.radius*_outerRadius);
            labelScale = (dataTransform.radius*_outerRadius - baseRadius)/oldTextSize;
        }

        //now, convert our baseRadius base to a scaled down fullRadius
        _radiusInPixelsAfterLabels = baseRadius/_outerRadius;

        for (i = 0; i < n; i++)
        {
            ld = labelData[i];
            ld.labelX = ld.labelX*labelScale  + o.x + (ld.labelCos*baseRadius);
            ld.labelY = ld.labelY*labelScale  + o.y + ld.labelSin*baseRadius;
        }
        _renderData.labelScale = labelScale;
        return null;
    }

    /**
     *  @private
     */
    private function renderInternalLabels(renderData:PieSeriesRenderData,renderCache:Array /* of PieSeriesItem */):void
    {
    	_labelCache.discard = true;
    	_labelCache.remove = true;
    	_labelCache.count = 0;
    	_labelCache.discard = false;
    	_labelCache.remove = false;
        var labelData:Object= renderData.labelData;
        
        if (!labelData)
            return;
        
        var visibleLabelSet:Array /* of PieSeriesItem */ = labelData.visibleLabels;

        var n:int = visibleLabelSet.length;

        _labelCache.count = (labelData.renderCallouts)? renderCache.length:visibleLabelSet.length;

        var labels:Array /* of IUITextField */ = _labelCache.instances;

        var i:int;
        var ld:PieSeriesItem;
        var label:IUITextField;
        var labelScale:Number = renderData.labelScale;

        for (i = 0; i < n; i++)
        {
            ld = visibleLabelSet[i];
            if (ld.percentValue == 0)
            	continue;
            label = labels[i];
            ld.label = label;
            label.text = ld.labelText;
            label.scaleX = label.scaleY = labelScale;
            label.x = ld.labelX;
            label.y = ld.labelY;
        }

        if (labelData.renderCallouts)
        {
            auxRenderCalloutLabels(renderData,labelData.calloutLabelData,visibleLabelSet.length);
        }
    }

    /**
     *  @private
     */
    private function measureInternalLabels(bCallout:Boolean,renderCache:Array /* of PieSeriesItem */):Object
    {
        //type
        var returnData:Object = null;
        var dataTransform:PolarTransform = PolarTransform(dataTransform);
        var n:int = renderCache.length;
        var rc:Rectangle= new Rectangle(0,0,unscaledWidth,unscaledHeight);
        var o:Point = dataTransform.origin;
        var removedLabels:Array /* of PieSeriesItem */ = [];
        var visibleLabels:Array /* of PieSeriesItem */ = [];
        var baseRadius:Number = _radiusInPixelsAfterLabels*_outerRadius* (1-_maxExplodeRadiusRatio) * .7;
        var labelData:Array /* of PieSeriesItem */ = [];
        var baseAngle:Number = _startAngleRadians;
        var i:int;
        var labelScale:Number = 1;

        var ld:PieSeriesItem;
        var label:IUITextField;

        for (i= 0; i < n; i++)
        {
            ld = renderCache[i];

            if (_labelFunction != null)
                _measuringField.text = ld.labelText = _labelFunction(renderCache[i].item,_field,i,renderCache[i].percentValue);
            else if (_labelField)
                _measuringField.text = ld.labelText = renderCache[i].item[_labelField];
            else
                _measuringField.text = ld.labelText = renderCache[i].value.toString();

            var renderDirection:String = getStyle('renderDirection');
        	if(renderDirection == "clockwise")
        		ld.labelAngle = (2*Math.PI - (baseAngle + renderCache[i].angle/2))%(2*Math.PI);
        	else
        		ld.labelAngle = (baseAngle + renderCache[i].angle/2)%(2*Math.PI);
        	
            ld.labelCos = Math.cos(ld.labelAngle);
            ld.labelSin = -Math.sin(ld.labelAngle);

            ld.labelWidth = _measuringField.width;
            ld.labelHeight = _measuringField.height;
            var wedgeExplodeRadiusInPixels:Number = calculateExplodeRadiusForWedge(i);
            ld.labelX =  ld.labelCos*(wedgeExplodeRadiusInPixels+baseRadius) + o.x - ld.labelWidth/2;
            ld.labelY =  ld.labelSin*(wedgeExplodeRadiusInPixels+baseRadius) + o.y - ld.labelHeight/2;

            if (i > 0)
            {
                ld.prev = labelData[i-1];
                ld.prev.next = ld;
            }

            labelData[i] = ld;
            baseAngle += renderCache[i].angle;
        }
        labelData[0].prev = labelData[n-1];
        labelData[n-1].next = labelData[0];


        var sortedLabels:Array /* of PieSeriesItem */ = labelData.concat();
        sortedLabels.sortOn("angle",Array.NUMERIC);


        // now we're going to go through each label and scale down to make sure it doesn't run into it's nearest neighbor

        var insideLabelSizeLimit:Number = getStyle("insideLabelSizeLimit");

        i = 0;

        var labelFormat:TextFormat = determineTextFormatFromStyles();
        while (i < sortedLabels.length)
        {
            ld = sortedLabels[i];
            label = ld.label;
            //type these
            var nextNeighbor:PieSeriesItem = ld.next;
            var prevNeighbor:PieSeriesItem = ld.prev;

            var rscale:Number = 1;
            // find out how much we would have to scale to get to the next neighbor up
            var RXScale:Number = Math.abs(ld.labelX - nextNeighbor.labelX)/(ld.labelWidth/2 + nextNeighbor.labelWidth/2);
            var RYScale:Number = Math.abs(ld.labelY - nextNeighbor.labelY)/(ld.labelHeight/2 + nextNeighbor.labelHeight/2);
            if (RXScale < 1 && RYScale < 1)
            {
                rscale = Math.max(RXScale,RYScale);
            }
            RXScale = Math.abs(ld.labelX - prevNeighbor.labelX)/(ld.labelWidth/2 + prevNeighbor.labelWidth/2);
            RYScale = Math.abs(ld.labelY - prevNeighbor.labelY)/(ld.labelHeight/2 + prevNeighbor.labelHeight/2);
            if (RXScale < 1 && RYScale < 1)
            {
                rscale = Math.min(rscale,Math.max(RXScale,RYScale));
            }

            if (rscale * Number(labelFormat.size) < insideLabelSizeLimit)
            {
                ld.prev.next = ld.next;
                ld.next.prev = ld.prev;
                removedLabels.push(ld);
                sortedLabels.splice(i,1);
                continue;
            }
            else
            {
                visibleLabels.push(ld);
                labelScale = Math.min(labelScale,rscale);
            }
            i++;
        }
        labelScale = Math.max(.6,labelScale);

        _renderData.labelScale = labelScale;

        if (bCallout == false)
        {
            returnData = { visibleLabels: visibleLabels, renderCallouts: false };
        }
        else
        {
            returnData = { visibleLabels: visibleLabels, renderCallouts: true,
                           calloutLabelData: auxMeasureCalloutLabels(_renderData, removedLabels) };
        }
        return returnData;
    }

    
/* Mouse handling*/
    private function calcAngle(x:Number,y:Number):Number
    {
        const twoMP:Number = Math.PI*2;

        var angle:Number;
        var at:Number = Math.atan(-y/x);
        if (x < 0)
            angle = at + Math.PI;
        else if (y < 0)
            angle = at;
        else
            angle = at + twoMP;

//      angle = (angle - _startAngleRadians) % (twoMP);
//      if (angle < 0)
//          angle += twoMP;
        return angle
    }

    /**
     *  @private
     */
    private function formatDataTip(hd:HitData):String
    {
        var dt:String = "";


        var sliceName:String = "";
        if (_nameField != "")
            sliceName = PieSeriesItem(hd.chartItem).item[_nameField];
        if (sliceName != "")
            dt += "<b>" + sliceName + ":</b> <b> " + Math.round(PieSeriesItem(hd.chartItem).percentValue*10)/10 + "%</b><BR/>";
        else
            dt += "<b>" + Math.round(PieSeriesItem(hd.chartItem).percentValue*10)/10 + "%</b><BR/>";


        dt += "<i>("+PieSeriesItem(hd.chartItem).value +")</i>";

        return dt;

    }

    /**
     *  @private
     */
    mx_internal function getRadiusInPixels():Number
    {
        return _radiusInPixelsScaledForExplode;
    }

    /**
     *  @private
     */
    mx_internal function getInnerRadiusInPixels():Number
    {
        return _innerRadiusInPixelsScaledForExplode;
    }
}

}

////////////////////////////////////////////////////////////////////////////////

import flash.display.Graphics;
import flash.geom.Rectangle;
import mx.charts.chartClasses.LegendData;
import mx.core.IDataRenderer;
import mx.graphics.IFill;
import mx.graphics.IStroke;
import mx.skins.ProgrammaticSkin;

/**
 *  @private
 */
class PieSeriesLegendData extends LegendData
{
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function PieSeriesLegendData()
    {
        super();
    }

    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    public var fill:IFill;
}

/**
 *  @private
 */
class PieSeriesLegendMarker extends ProgrammaticSkin implements IDataRenderer
{
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function PieSeriesLegendMarker()
    {
        super();
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  data
    //----------------------------------

    /**
     *  @private
     */
    private var _data:Object;

    [Inspectable(category="General")]

    /**
     *  @private
     */
    public function get data():Object
    {
        return _data;
    }

    /**
     *  @private
     */
    public function set data(value:Object):void
    {
        _data = value;

        invalidateDisplayList();
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods: UIComponent
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override protected function updateDisplayList(unscaledWidth:Number,
                                                  unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth,unscaledHeight);

        var stroke:IStroke = getStyle("stroke");
        
        var fill:IFill;
        if (_data != null && "fill" in _data)
            fill = _data.fill;
        else
            fill = getStyle("fill");

        var g:Graphics = graphics;
        
        g.clear();
        g.moveTo(0, 0);
        
        if (stroke)
            stroke.apply(g,null,null);
        else
            g.lineStyle(0, 0, 0);
        
        if (fill)
            fill.begin(g,  new Rectangle(0, 0, width, height), null);
        
        g.lineTo(width, 0);
        g.lineTo(width, height);
        g.lineTo(0, height);
        g.lineTo(0, 0);
        
        if (fill)
            fill.end(g);
    }

}

