826 lines
25 KiB
ActionScript
Executable File
826 lines
25 KiB
ActionScript
Executable File
/*
|
|
Copyright (c) 2009 Yahoo! Inc. All rights reserved.
|
|
The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license
|
|
*/
|
|
package com.yahoo.astra.fl.charts
|
|
{
|
|
import com.yahoo.astra.fl.charts.events.ChartEvent;
|
|
import com.yahoo.astra.fl.charts.legend.ILegend;
|
|
import com.yahoo.astra.fl.charts.legend.LegendItemData;
|
|
import com.yahoo.astra.fl.charts.series.ICategorySeries;
|
|
import com.yahoo.astra.fl.charts.series.ILegendItemSeries;
|
|
import com.yahoo.astra.fl.charts.series.ISeries;
|
|
import com.yahoo.astra.fl.charts.series.ISeriesItemRenderer;
|
|
import com.yahoo.astra.fl.utils.UIComponentUtil;
|
|
|
|
import fl.core.InvalidationType;
|
|
import fl.core.UIComponent;
|
|
|
|
import flash.accessibility.AccessibilityProperties;
|
|
import flash.display.DisplayObject;
|
|
import flash.display.Sprite;
|
|
import flash.events.Event;
|
|
import flash.events.MouseEvent;
|
|
import flash.geom.Point;
|
|
import flash.text.TextFormat;
|
|
import flash.text.TextFormatAlign;
|
|
import flash.utils.getDefinitionByName;
|
|
|
|
//--------------------------------------
|
|
// Styles
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* The padding that separates the border of the component from its contents,
|
|
* in pixels.
|
|
*
|
|
* @default 10
|
|
*/
|
|
[Style(name="contentPadding", type="Number")]
|
|
|
|
/**
|
|
* Name of the class to use as the skin for the background and border of the
|
|
* component.
|
|
*
|
|
* @default ChartBackgroundSkin
|
|
*/
|
|
[Style(name="backgroundSkin", type="Class")]
|
|
|
|
/**
|
|
* The default colors for each series. These colors are used for markers,
|
|
* in most cases, but they may apply to lines, fills, or other graphical
|
|
* items.
|
|
*
|
|
* <p>An Array of values that correspond to series indices in the data
|
|
* provider. If the number of values in the Array is less than the number
|
|
* of series, then the next series will restart at index zero in the style
|
|
* Array. If the value of this style is an empty Array, then each individual series
|
|
* will use the default or modified value set on the series itself.</p>
|
|
*
|
|
* <p>Example: If the seriesColors style is equal to [0xffffff, 0x000000] and there
|
|
* are three series in the chart's data provider, then the series at index 0
|
|
* will have a color of 0xffffff, index 1 will have a color of 0x000000, and
|
|
* index 2 will have a color of 0xffffff (starting over from the beginning).</p>
|
|
*
|
|
* @default [0x00b8bf, 0x8dd5e7, 0xedff9f, 0xffa928, 0xc0fff6, 0xd00050, 0xc6c6c6, 0xc3eafb, 0xfcffad, 0xcfff83, 0x444444, 0x4d95dd, 0xb8ebff, 0x60558f, 0x737d7e, 0xa64d9a, 0x8e9a9b, 0x803e77]
|
|
*/
|
|
[Style(name="seriesColors", type="Array")]
|
|
|
|
/**
|
|
* The default size of the markers in pixels. The actual drawn size of the
|
|
* markers could end up being different in some cases. For example, bar charts
|
|
* and column charts display markers side-by-side, and a chart may need to make
|
|
* the bars or columns smaller to fit within the required region.
|
|
*
|
|
* <p>An Array of values that correspond to series indices in the data
|
|
* provider. If the number of values in the Array is less than the number
|
|
* of series, then the next series will restart at index zero in the style
|
|
* Array. If the value of this style is an empty Array, then each individual series
|
|
* will use the default or modified value set on the series itself.</p>
|
|
*
|
|
* <p>Example: If the seriesMarkerSizes style is equal to [10, 15] and there
|
|
* are three series in the chart's data provider, then the series at index 0
|
|
* will have a marker size of 10, index 1 will have a marker size of 15, and
|
|
* index 2 will have a marker size of 10 (starting over from the beginning).</p>
|
|
*
|
|
* @default []
|
|
*/
|
|
[Style(name="seriesMarkerSizes", type="Array")]
|
|
|
|
/**
|
|
* An Array containing the default skin classes for each series. These classes
|
|
* are used to instantiate the marker skins. The values may be fully-qualified
|
|
* package and class strings or a reference to the classes themselves.
|
|
*
|
|
* <p>An Array of values that correspond to series indices in the data
|
|
* provider. If the number of values in the Array is less than the number
|
|
* of series, then the next series will restart at index zero in the style
|
|
* Array. If the value of this style is an empty Array, then each individual series
|
|
* will use the default or modified value set on the series itself.</p>
|
|
*
|
|
* <p>Example: If the seriesMarkerSkins style is equal to [CircleSkin, DiamondSkin] and there
|
|
* are three series in the chart's data provider, then the series at index 0
|
|
* will have a marker skin of CircleSkin, index 1 will have a marker skin of DiamondSkin, and
|
|
* index 2 will have a marker skin of CircleSkin (starting over from the beginning).</p>
|
|
*
|
|
* @default []
|
|
*/
|
|
[Style(name="seriesMarkerSkins", type="Array")]
|
|
|
|
/**
|
|
* The TextFormat object to use to render data tips.
|
|
*
|
|
* @default TextFormat("_sans", 11, 0x000000, false, false, false, '', '', TextFormatAlign.LEFT, 0, 0, 0, 0)
|
|
*/
|
|
[Style(name="dataTipTextFormat", type="TextFormat")]
|
|
|
|
/**
|
|
* Name of the class to use as the skin for the background and border of the
|
|
* chart's data tip.
|
|
*
|
|
* @default ChartDataTipBackground
|
|
*/
|
|
[Style(name="dataTipBackgroundSkin", type="Class")]
|
|
|
|
/**
|
|
* If the datatip's content padding is customizable, it will use this value.
|
|
* The padding that separates the border of the component from its contents,
|
|
* in pixels.
|
|
*
|
|
* @default 6
|
|
*/
|
|
[Style(name="dataTipContentPadding", type="Number")]
|
|
|
|
/**
|
|
* Determines if data changes should be displayed with animation.
|
|
*
|
|
* @default true
|
|
*/
|
|
[Style(name="animationEnabled", type="Boolean")]
|
|
|
|
/**
|
|
* Indicates whether embedded font outlines are used to render the text
|
|
* field. If this value is true, Flash Player renders the text field by
|
|
* using embedded font outlines. If this value is false, Flash Player
|
|
* renders the text field by using device fonts.
|
|
*
|
|
* If you set the embedFonts property to true for a text field, you must
|
|
* specify a font for that text by using the font property of a TextFormat
|
|
* object that is applied to the text field. If the specified font is not
|
|
* embedded in the SWF file, the text is not displayed.
|
|
*
|
|
* @default false
|
|
*/
|
|
[Style(name="embedFonts", type="Boolean")]
|
|
|
|
/**
|
|
* Functionality common to most charts. Generally, a <code>Chart</code> object
|
|
* shouldn't be instantiated directly. Instead, a subclass with a concrete
|
|
* implementation should be used. That subclass generally should implement the
|
|
* <code>IPlotArea</code> interface.
|
|
*
|
|
* @author Josh Tynjala
|
|
*/
|
|
public class Chart extends UIComponent
|
|
{
|
|
|
|
//--------------------------------------
|
|
// Class Variables
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
private static var defaultStyles:Object =
|
|
{
|
|
seriesMarkerSizes: null,
|
|
seriesMarkerSkins: null,
|
|
seriesColors:
|
|
[
|
|
0x00b8bf, 0x8dd5e7, 0xedff9f, 0xffa928, 0xc0fff6, 0xd00050,
|
|
0xc6c6c6, 0xc3eafb, 0xfcffad, 0xcfff83, 0x444444, 0x4d95dd,
|
|
0xb8ebff, 0x60558f, 0x737d7e, 0xa64d9a, 0x8e9a9b, 0x803e77
|
|
],
|
|
seriesBorderColors:[],
|
|
seriesFillColors:[],
|
|
seriesLineColors:[],
|
|
seriesBorderAlphas:[1],
|
|
seriesFillAlphas:[1],
|
|
seriesLineAlphas:[1],
|
|
contentPadding: 10,
|
|
backgroundSkin: "ChartBackground",
|
|
backgroundColor: 0xffffff,
|
|
dataTipBackgroundSkin: "ChartDataTipBackground",
|
|
dataTipContentPadding: 6,
|
|
dataTipTextFormat: new TextFormat("_sans", 11, 0x000000, false, false, false, '', '', TextFormatAlign.LEFT, 0, 0, 0, 0),
|
|
animationEnabled: true,
|
|
embedFonts: false
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
private static const ALL_SERIES_STYLES:Object =
|
|
{
|
|
color: "seriesColors",
|
|
markerSize: "seriesMarkerSizes",
|
|
markerSkin: "seriesMarkerSkins",
|
|
borderColor: "seriesBorderColors",
|
|
fillColor: "seriesFillColors",
|
|
lineColor: "seriesLineColors",
|
|
borderAlpha: "seriesBorderAlphas",
|
|
fillAlpha: "seriesFillAlphas",
|
|
lineAlpha: "seriesLineAlphas"
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
private static const SHARED_SERIES_STYLES:Object =
|
|
{
|
|
animationEnabled: "animationEnabled"
|
|
};
|
|
|
|
private static const DATA_TIP_STYLES:Object =
|
|
{
|
|
backgroundSkin: "dataTipBackgroundSkin",
|
|
contentPadding: "dataTipContentPadding",
|
|
textFormat: "dataTipTextFormat",
|
|
embedFonts: "embedFonts"
|
|
};
|
|
|
|
//--------------------------------------
|
|
// Class Methods
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
* @copy fl.core.UIComponent#getStyleDefinition()
|
|
*/
|
|
public static function getStyleDefinition():Object
|
|
{
|
|
return mergeStyles(defaultStyles, UIComponent.getStyleDefinition());
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Constructor
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function Chart()
|
|
{
|
|
super();
|
|
this.accessibilityProperties = new AccessibilityProperties();
|
|
this.accessibilityProperties.forceSimple = true;
|
|
this.accessibilityProperties.description = "Chart";
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Variables and Properties
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
* The display object representing the chart background.
|
|
*/
|
|
protected var background:DisplayObject;
|
|
|
|
/**
|
|
* @private
|
|
* The area where series are drawn.
|
|
*/
|
|
protected var content:Sprite;
|
|
|
|
/**
|
|
* @private
|
|
* The mouse over data tip that displays information about an item on the chart.
|
|
*/
|
|
protected var dataTip:DisplayObject;
|
|
|
|
/**
|
|
* @private
|
|
* Storage for the data property. Saves a copy of the unmodified data.
|
|
*/
|
|
private var _dataProvider:Object;
|
|
|
|
/**
|
|
* @private
|
|
* Modified version of the stored data.
|
|
*/
|
|
protected var series:Array = [];
|
|
|
|
[Inspectable(type=Array)]
|
|
/**
|
|
* @copy com.yahoo.astra.fl.charts.IChart#dataProvider
|
|
*/
|
|
public function get dataProvider():Object
|
|
{
|
|
return this.series;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
public function set dataProvider(value:Object):void
|
|
{
|
|
if(this._dataProvider != value)
|
|
{
|
|
this._dataProvider = value;
|
|
this.invalidate(InvalidationType.DATA);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Storage for the defaultSeriesType property.
|
|
*/
|
|
private var _defaultSeriesType:Class;
|
|
|
|
/**
|
|
* When raw data (like an Array of Numbers) is encountered where an
|
|
* ISeries instance is expected, it will be converted to this default
|
|
* type. Accepts either a Class instance or a String referencing a
|
|
* fully-qualified class name.
|
|
*/
|
|
public function get defaultSeriesType():Object
|
|
{
|
|
return this._defaultSeriesType;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
public function set defaultSeriesType(value:Object):void
|
|
{
|
|
if(!value) return;
|
|
var classDefinition:Class = null;
|
|
if(value is Class)
|
|
{
|
|
classDefinition = value as Class;
|
|
}
|
|
else
|
|
{
|
|
// borrowed from fl.core.UIComponent#getDisplayObjectInstance()
|
|
try
|
|
{
|
|
classDefinition = getDefinitionByName(value.toString()) as Class;
|
|
}
|
|
catch(e:Error)
|
|
{
|
|
try
|
|
{
|
|
classDefinition = this.loaderInfo.applicationDomain.getDefinition(value.toString()) as Class;
|
|
}
|
|
catch (e:Error)
|
|
{
|
|
// Nothing
|
|
}
|
|
}
|
|
}
|
|
|
|
this._defaultSeriesType = classDefinition;
|
|
//no need to redraw.
|
|
//if the series have already been created, the user probably wanted it that way.
|
|
//we have no way to tell if the user chose a particular series' type or not anyway.
|
|
}
|
|
|
|
private var _lastDataTipRenderer:ISeriesItemRenderer;
|
|
|
|
/**
|
|
* @private
|
|
* Storage for the dataTipFunction property.
|
|
*/
|
|
private var _dataTipFunction:Function = defaultDataTipFunction;
|
|
|
|
/**
|
|
* If defined, the chart will call the input function to determine the
|
|
* text displayed in the chart's data tip. The function uses the following
|
|
* signature:
|
|
*
|
|
* <p><code>function dataTipFunction(item:Object, index:int, series:ISeries):String</code></p>
|
|
*/
|
|
public function get dataTipFunction():Function
|
|
{
|
|
return this._dataTipFunction;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
public function set dataTipFunction(value:Function):void
|
|
{
|
|
this._dataTipFunction = value;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Storage for the legend property.
|
|
*/
|
|
private var _legend:ILegend;
|
|
|
|
/**
|
|
* The component that will display a human-readable legend for the chart.
|
|
*/
|
|
public function get legend():ILegend
|
|
{
|
|
return this._legend;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
public function set legend(value:ILegend):void
|
|
{
|
|
this._legend = value;
|
|
this.invalidate();
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Public Methods
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* Returns the index within this plot area of the input ISeries object.
|
|
*
|
|
* @param series a series that is displayed in this plot area.
|
|
* @return the index of the input series
|
|
*/
|
|
public function seriesToIndex(series:ISeries):int
|
|
{
|
|
return this.series.indexOf(series);
|
|
}
|
|
|
|
/**
|
|
* Returns the ISeries object at the specified index.
|
|
*
|
|
* @param index the index of the series to return
|
|
* @return the series that appears at the input index or null if out of bounds
|
|
*/
|
|
public function indexToSeries(index:int):ISeries
|
|
{
|
|
if(index < 0 || index >= this.series.length) return null;
|
|
return this.series[index];
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Protected Methods
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
override protected function configUI():void
|
|
{
|
|
super.width = 400;
|
|
super.height = 300;
|
|
|
|
super.configUI();
|
|
|
|
this.content = new Sprite();
|
|
this.addChild(this.content);
|
|
|
|
this.dataTip = new DataTipRenderer();
|
|
this.dataTip.visible = false;
|
|
this.addChild(this.dataTip);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
override protected function draw():void
|
|
{
|
|
var dataInvalid:Boolean = this.isInvalid(InvalidationType.DATA);
|
|
var stylesInvalid:Boolean = this.isInvalid(InvalidationType.STYLES);
|
|
var sizeInvalid:Boolean = this.isInvalid(InvalidationType.SIZE);
|
|
|
|
if(stylesInvalid || dataInvalid)
|
|
{
|
|
this.refreshSeries();
|
|
}
|
|
|
|
//update the background if needed
|
|
if(stylesInvalid)
|
|
{
|
|
if(this.background)
|
|
{
|
|
this.removeChild(this.background);
|
|
}
|
|
var skinClass:Object = this.getStyleValue("backgroundSkin");
|
|
this.background = UIComponentUtil.getDisplayObjectInstance(this, skinClass);
|
|
this.addChildAt(this.background, 0);
|
|
}
|
|
|
|
if(this.background && (stylesInvalid || sizeInvalid))
|
|
{
|
|
this.background.width = this.width;
|
|
this.background.height = this.height;
|
|
|
|
//force the background to redraw if it is a UIComponent
|
|
if(this.background is UIComponent)
|
|
{
|
|
(this.background as UIComponent).drawNow();
|
|
}
|
|
}
|
|
|
|
if(this.dataTip is UIComponent)
|
|
{
|
|
var dataTip:UIComponent = UIComponent(this.dataTip);
|
|
this.copyStylesToChild(dataTip, DATA_TIP_STYLES);
|
|
dataTip.drawNow();
|
|
}
|
|
|
|
super.draw();
|
|
}
|
|
|
|
/**
|
|
* Analyzes the input data and smartly converts it to the correct ISeries type
|
|
* required for drawing. Adds new ISeries objects to the display list and removes
|
|
* unused series objects that no longer need to be drawn.
|
|
*/
|
|
protected function refreshSeries():void
|
|
{
|
|
var modifiedData:Object = this._dataProvider;
|
|
|
|
//loop through each series and convert it to the correct data type
|
|
if(modifiedData is Array)
|
|
{
|
|
var arrayData:Array = (modifiedData as Array).concat();
|
|
var seriesCount:int = arrayData.length;
|
|
var foundIncompatibleData:Boolean = false;
|
|
for(var i:int = 0; i < seriesCount; i++)
|
|
{
|
|
var currentItem:Object = arrayData[i];
|
|
if(currentItem is Array || currentItem is XMLList)
|
|
{
|
|
var itemSeries:ISeries = new this.defaultSeriesType();
|
|
if(currentItem is Array)
|
|
{
|
|
itemSeries.dataProvider = (currentItem as Array).concat();
|
|
}
|
|
else if(currentItem is XMLList)
|
|
{
|
|
itemSeries.dataProvider = (currentItem as XMLList).copy();
|
|
}
|
|
arrayData[i] = itemSeries;
|
|
}
|
|
else if(!(currentItem is ISeries))
|
|
{
|
|
//we only support Array, XMLList, and ISeries
|
|
//anything else means that we should restore the original data
|
|
var originalData:Array = (modifiedData as Array).concat();
|
|
modifiedData = new this.defaultSeriesType(originalData);
|
|
foundIncompatibleData = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!foundIncompatibleData)
|
|
{
|
|
modifiedData = arrayData;
|
|
}
|
|
}
|
|
|
|
//attempt to turn a string into XML
|
|
if(modifiedData is String)
|
|
{
|
|
try
|
|
{
|
|
modifiedData = new XML(modifiedData);
|
|
}
|
|
catch(error:Error)
|
|
{
|
|
//this isn't a valid xml string, so ignore it
|
|
return;
|
|
}
|
|
}
|
|
|
|
//we need an XMLList, so get the elements
|
|
if(modifiedData is XML)
|
|
{
|
|
modifiedData = (modifiedData as XML).elements();
|
|
}
|
|
|
|
//convert the XMLList to a series
|
|
if(modifiedData is XMLList)
|
|
{
|
|
modifiedData = new this.defaultSeriesType(modifiedData);
|
|
}
|
|
|
|
//we should have an ISeries object by now, so put it in an Array
|
|
if(modifiedData is ISeries)
|
|
{
|
|
//if the main data is a series, put it in an array
|
|
modifiedData = [modifiedData];
|
|
}
|
|
|
|
//if it's not an array, we have bad data, so ignore it
|
|
if(!(modifiedData is Array))
|
|
{
|
|
return;
|
|
}
|
|
|
|
arrayData = modifiedData as Array;
|
|
|
|
seriesCount = this.series.length;
|
|
for(i = 0; i < seriesCount; i++)
|
|
{
|
|
var currentSeries:ISeries = this.series[i] as ISeries;
|
|
if(arrayData.indexOf(currentSeries) < 0)
|
|
{
|
|
//if the series no longer exists, remove it from the display list and stop listening to it
|
|
this.content.removeChild(DisplayObject(currentSeries));
|
|
currentSeries.removeEventListener("dataChange", seriesDataChangeHandler);
|
|
currentSeries.removeEventListener(ChartEvent.ITEM_ROLL_OVER, chartItemRollOver);
|
|
currentSeries.removeEventListener(ChartEvent.ITEM_ROLL_OUT, chartItemRollOut);
|
|
currentSeries.chart = null;
|
|
}
|
|
}
|
|
|
|
//rebuild the series Array
|
|
this.series = [];
|
|
seriesCount = arrayData.length;
|
|
for(i = 0; i < seriesCount; i++)
|
|
{
|
|
currentSeries = arrayData[i] as ISeries;
|
|
this.series.push(currentSeries);
|
|
if(!this.contains(DisplayObject(currentSeries)))
|
|
{
|
|
//if this is a new series, add it to the display list and listen for events
|
|
currentSeries.addEventListener("dataChange", seriesDataChangeHandler, false, 0, true);
|
|
currentSeries.addEventListener(ChartEvent.ITEM_ROLL_OVER, chartItemRollOver, false, 0, true);
|
|
currentSeries.addEventListener(ChartEvent.ITEM_ROLL_OUT, chartItemRollOut, false, 0, true);
|
|
currentSeries.chart = this;
|
|
this.content.addChild(DisplayObject(currentSeries));
|
|
}
|
|
|
|
DisplayObject(currentSeries).x = 0;
|
|
DisplayObject(currentSeries).y = 0;
|
|
|
|
//make sure the series are displayed in the correct order
|
|
this.content.setChildIndex(DisplayObject(currentSeries), this.content.numChildren - 1);
|
|
|
|
//update the series styles
|
|
this.copyStylesToSeries(currentSeries, ALL_SERIES_STYLES);
|
|
if(currentSeries is UIComponent)
|
|
{
|
|
this.copyStylesToChild(UIComponent(currentSeries), SHARED_SERIES_STYLES);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Refreshes the legend's data provider.
|
|
*/
|
|
protected function updateLegend():void
|
|
{
|
|
if(!this.legend) return;
|
|
|
|
var legendData:Array = [];
|
|
var seriesCount:int = this.series.length;
|
|
for(var i:int = 0; i < seriesCount; i++)
|
|
{
|
|
var series:ISeries = ISeries(this.series[i]);
|
|
if(series is ILegendItemSeries)
|
|
{
|
|
var itemData:LegendItemData = ILegendItemSeries(series).createLegendItemData();
|
|
itemData.label = itemData.label ? itemData.label : i.toString();
|
|
legendData.push(itemData);
|
|
}
|
|
else if(series is ICategorySeries)
|
|
{
|
|
legendData = legendData.concat(ICategorySeries(series).createLegendItemData());
|
|
}
|
|
}
|
|
|
|
this.legend.dataProvider = legendData;
|
|
|
|
if(UIComponent.inCallLaterPhase)
|
|
{
|
|
UIComponent(this.legend).drawNow();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Tranfers the chart's styles to the ISeries components it contains. These styles
|
|
* must be of the type Array, and the series index determines the index of the value
|
|
* to use from that Array. If the chart contains more ISeries components than there
|
|
* are values in the Array, the indices are reused starting from zero.
|
|
*/
|
|
protected function copyStylesToSeries(child:ISeries, styleMap:Object):void
|
|
{
|
|
var index:int = this.series.indexOf(child);
|
|
var childComponent:UIComponent = child as UIComponent;
|
|
for(var n:String in styleMap)
|
|
{
|
|
var styleValues:Array = this.getStyleValue(styleMap[n]) as Array;
|
|
|
|
//if it doesn't exist, ignore it and go with the defaults for this series
|
|
if(styleValues == null || styleValues.length == 0) continue;
|
|
childComponent.setStyle(n, styleValues[index % styleValues.length])
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
protected function defaultDataTipFunction(item:Object, index:int, series:ISeries):String
|
|
{
|
|
if(series.displayName)
|
|
{
|
|
return series.displayName;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Passes data to the data tip.
|
|
*/
|
|
protected function refreshDataTip():void
|
|
{
|
|
var item:Object = this._lastDataTipRenderer.data;
|
|
var series:ISeries = this._lastDataTipRenderer.series;
|
|
var index:int = series.itemRendererToIndex(this._lastDataTipRenderer);
|
|
|
|
var dataTipText:String = "";
|
|
if(this.dataTipFunction != null)
|
|
{
|
|
dataTipText = this.dataTipFunction(item, index, series);
|
|
}
|
|
|
|
var dataTipRenderer:IDataTipRenderer = this.dataTip as IDataTipRenderer;
|
|
dataTipRenderer.text = dataTipText;
|
|
dataTipRenderer.data = item;
|
|
|
|
this.setChildIndex(this.dataTip, this.numChildren - 1);
|
|
if(this.dataTip is UIComponent)
|
|
{
|
|
UIComponent(this.dataTip).drawNow();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Protected Event Handlers
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
* Display the data tip when the user moves the mouse over a chart marker.
|
|
*/
|
|
protected function chartItemRollOver(event:ChartEvent):void
|
|
{
|
|
this._lastDataTipRenderer = event.itemRenderer;
|
|
this.refreshDataTip();
|
|
|
|
var position:Point = this.mousePositionToDataTipPosition();
|
|
this.dataTip.x = position.x;
|
|
this.dataTip.y = position.y;
|
|
this.dataTip.visible = true;
|
|
|
|
this.stage.addEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler, false, 0 ,true);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Hide the data tip when the user moves the mouse off a chart marker.
|
|
*/
|
|
protected function chartItemRollOut(event:ChartEvent):void
|
|
{
|
|
this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler);
|
|
this.dataTip.visible = false;
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Private Methods
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
* Determines the position for the data tip based on the mouse position
|
|
* and the bounds of the chart. Attempts to keep the data tip within the
|
|
* chart bounds so that it isn't hidden by any other display objects.
|
|
*/
|
|
private function mousePositionToDataTipPosition():Point
|
|
{
|
|
var position:Point = new Point();
|
|
position.x = this.mouseX + 2;
|
|
position.x = Math.min(this.width - this.dataTip.width, position.x);
|
|
position.y = this.mouseY - this.dataTip.height - 2;
|
|
position.y = Math.max(0, position.y);
|
|
return position;
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Private Event Handlers
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
* The plot area needs to redraw the axes if series data changes.
|
|
*/
|
|
private function seriesDataChangeHandler(event:Event):void
|
|
{
|
|
this.invalidate(InvalidationType.DATA);
|
|
if(this.dataTip.visible)
|
|
{
|
|
this.refreshDataTip();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Make the data tip follow the mouse.
|
|
*/
|
|
private function stageMouseMoveHandler(event:MouseEvent):void
|
|
{
|
|
var position:Point = this.mousePositionToDataTipPosition();
|
|
this.dataTip.x = position.x;
|
|
this.dataTip.y = position.y;
|
|
}
|
|
|
|
}
|
|
}
|