536 lines
14 KiB
ActionScript
Executable File
536 lines
14 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.series
|
|
{
|
|
import com.yahoo.astra.fl.charts.events.ChartEvent;
|
|
|
|
import fl.core.InvalidationType;
|
|
import fl.core.UIComponent;
|
|
import fl.transitions.easing.Strong;
|
|
|
|
import flash.display.DisplayObject;
|
|
import flash.display.InteractiveObject;
|
|
import flash.display.Shape;
|
|
import flash.events.Event;
|
|
import flash.events.MouseEvent;
|
|
import flash.utils.Dictionary;
|
|
import flash.utils.getQualifiedClassName;
|
|
|
|
//--------------------------------------
|
|
// Styles
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* The easing function for animations that occur on data changes.
|
|
*/
|
|
[Style(name="animationEasingFunction", type="Function")]
|
|
|
|
/**
|
|
* The duration for animations that occur on data changes.
|
|
*/
|
|
[Style(name="animationDuration", type="int")]
|
|
|
|
/**
|
|
* If true, data changes will be displayed with animations. If false, changes will happen instantly.
|
|
*/
|
|
[Style(name="animationEnabled", type="Boolean")]
|
|
|
|
/**
|
|
* The base color used by objects displayed in this series.
|
|
*/
|
|
[Style(name="color", type="uint")]
|
|
|
|
/**
|
|
* The border color used by programatic skins in this series.
|
|
*/
|
|
[Style(name="borderColor", type="uint")]
|
|
|
|
/**
|
|
* The fill color used by programatic skins in this series.
|
|
*/
|
|
[Style(name="fillColor", type="uint")]
|
|
|
|
/**
|
|
* The Class used to instantiate each marker's skin.
|
|
*/
|
|
[Style(name="markerSkin", type="Class")]
|
|
|
|
/**
|
|
* The size, in pixels, of each marker.
|
|
*/
|
|
[Style(name="markerSize", type="Number")]
|
|
|
|
/**
|
|
* The alpha value from 0.0 to 1.0 to use for drawing the markers.
|
|
*/
|
|
[Style(name="markerAlpha", type="Number")]
|
|
|
|
/**
|
|
* The alpha value from 0.0 to 1.0 to use for drawing the fills of markers.
|
|
*/
|
|
[Style(name="fillAlpha", type="Number")]
|
|
|
|
/**
|
|
* The alpha value from 0.0 to 1.0 to use for drawing the border of markers.
|
|
*/
|
|
[Style(name="borderAlpha", type="Number")]
|
|
|
|
/**
|
|
* Functionality common to most series. Generally, a <code>Series</code> object
|
|
* shouldn't be instantiated directly. Instead, a subclass with a concrete
|
|
* implementation should be used.
|
|
*
|
|
* @author Josh Tynjala
|
|
*/
|
|
public class Series extends UIComponent implements ISeries
|
|
{
|
|
|
|
//--------------------------------------
|
|
// Class Variables
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
private static var defaultStyles:Object =
|
|
{
|
|
markerSkin: Shape, //an empty display object
|
|
fillColor: null,
|
|
markerSize: 10,
|
|
markerAlpha: 1.0,
|
|
fillAlpha: 1.0,
|
|
borderAlpha: 1.0,
|
|
animationEnabled: true,
|
|
animationEasingFunction: fl.transitions.easing.Strong.easeOut,
|
|
animationDuration: 500,
|
|
borderColor: null,
|
|
color: 0x00b8bf
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
private static const RENDERER_STYLES:Object =
|
|
{
|
|
skin: "markerSkin",
|
|
fillColor: "fillColor",
|
|
borderColor: "borderColor",
|
|
color: "color",
|
|
fillAlpha: "fillAlpha",
|
|
borderAlpha: "borderAlpha"
|
|
};
|
|
|
|
//--------------------------------------
|
|
// Class Methods
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @copy fl.core.UIComponent#getStyleDefinition()
|
|
*/
|
|
public static function getStyleDefinition():Object
|
|
{
|
|
return mergeStyles(defaultStyles, UIComponent.getStyleDefinition());
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Constructor
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function Series(dataProvider:Object = null)
|
|
{
|
|
super();
|
|
this._dataProvider = dataProvider;
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Properties
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
protected var markers:Array = [];
|
|
|
|
/**
|
|
* @private
|
|
* A set of flags to indicate if special considerations need to be taken for the markers.
|
|
*/
|
|
protected var markerInvalidHash:Dictionary = new Dictionary(true);
|
|
|
|
/**
|
|
* @private
|
|
* Storage for the chart property.
|
|
*/
|
|
private var _chart:Object;
|
|
|
|
/**
|
|
* @copy com.yahoo.astra.fl.charts.ISeries#chart
|
|
*/
|
|
public function get chart():Object
|
|
{
|
|
return this._chart;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
public function set chart(value:Object):void
|
|
{
|
|
this._chart = value;
|
|
//this is a fun hack to ensure that series know if their parent charts are in live preview
|
|
if(this._chart == null || this._chart.parent == null)
|
|
{
|
|
this.isLivePreview = false;
|
|
}
|
|
var className:String;
|
|
try
|
|
{
|
|
className = getQualifiedClassName(this._chart.parent);
|
|
}
|
|
catch (e:Error) {}
|
|
this.isLivePreview = (className == "fl.livepreview::LivePreviewParent");
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* A lookup system to convert from an item to its item renderer.
|
|
*/
|
|
private var _itemToItemRendererHash:Dictionary = new Dictionary();
|
|
|
|
/**
|
|
* @private
|
|
* Storage for the itemRenderer property.
|
|
*/
|
|
private var _itemRenderer:Object = SeriesItemRenderer;
|
|
|
|
/**
|
|
* The class used to instantiate item renderers.
|
|
*/
|
|
public function get itemRenderer():Object
|
|
{
|
|
return this._itemRenderer;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
public function set itemRenderer(value:Object):void
|
|
{
|
|
if(this._itemRenderer != value)
|
|
{
|
|
this._itemRenderer = value;
|
|
this.invalidate("itemRenderer");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Storage for the data property.
|
|
*/
|
|
private var _dataProvider:Object;
|
|
|
|
/**
|
|
* @copy com.yahoo.astra.fl.charts.ISeries#data
|
|
*/
|
|
public function get dataProvider():Object
|
|
{
|
|
return this._dataProvider;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
public function set dataProvider(value:Object):void
|
|
{
|
|
if(this._dataProvider != value)
|
|
{
|
|
//if we get XML data and it isn't an XMLList,
|
|
//ignore the root tag
|
|
if(value is XML && !(value is XMLList))
|
|
{
|
|
value = value.elements();
|
|
}
|
|
|
|
if(value is XMLList)
|
|
{
|
|
value = XMLList(value).copy();
|
|
}
|
|
else if(value is Array)
|
|
{
|
|
value = (value as Array).concat();
|
|
}
|
|
|
|
this._dataProvider = value;
|
|
this.dispatchEvent(new Event("dataChange"));
|
|
this.invalidate(InvalidationType.DATA);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Storage for the displayName property.
|
|
*/
|
|
private var _displayName:String;
|
|
|
|
/**
|
|
* @copy com.yahoo.astra.fl.charts.ISeries#data
|
|
*/
|
|
public function get displayName():String
|
|
{
|
|
return this._displayName;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
public function set displayName(value:String):void
|
|
{
|
|
this._displayName = value;
|
|
}
|
|
|
|
/**
|
|
* @copy com.yahoo.astra.fl.charts.ISeries#length
|
|
*/
|
|
public function get length():int
|
|
{
|
|
if(this._dataProvider is Array)
|
|
{
|
|
return (this._dataProvider as Array).length;
|
|
}
|
|
else if(this._dataProvider is XMLList)
|
|
{
|
|
return (this._dataProvider as XMLList).length();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Public Methods
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @copy com.yahoo.astra.fl.charts.ISeries#clone()
|
|
*/
|
|
public function clone():ISeries
|
|
{
|
|
var series:Series = new Series();
|
|
series.dataProvider = this.dataProvider;
|
|
series.displayName = this.displayName;
|
|
return series;
|
|
}
|
|
|
|
/**
|
|
* @copy com.yahoo.astra.fl.charts.ISeries#itemRendererToIndex()
|
|
*/
|
|
public function itemRendererToIndex(renderer:ISeriesItemRenderer):int
|
|
{
|
|
return this.markers.indexOf(renderer);
|
|
}
|
|
|
|
public function itemToItemRenderer(item:Object):ISeriesItemRenderer
|
|
{
|
|
return this._itemToItemRendererHash[item] as ISeriesItemRenderer;
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Protected Methods
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
override protected function draw():void
|
|
{
|
|
//the class for the item renderers has changed. remove all markers
|
|
//so that they may be recreated.
|
|
if(this.isInvalid("itemRenderer"))
|
|
{
|
|
this.removeAllMarkers();
|
|
}
|
|
|
|
if(this.isInvalid("itemRenderer", InvalidationType.DATA, InvalidationType.STYLES))
|
|
{
|
|
this.refreshMarkers();
|
|
this._itemToItemRendererHash = new Dictionary(true);
|
|
var itemCount:int = this.markers.length;
|
|
for(var i:int = 0; i < itemCount; i++)
|
|
{
|
|
var marker:ISeriesItemRenderer = this.markers[i] as ISeriesItemRenderer;
|
|
if(this.isInvalid(InvalidationType.DATA)) //update data if needed
|
|
{
|
|
marker.data = this.dataProvider[i];
|
|
}
|
|
this._itemToItemRendererHash[marker.data] = marker;
|
|
|
|
var markerComponent:UIComponent = marker as UIComponent;
|
|
this.copyStylesToChild(markerComponent, RENDERER_STYLES);
|
|
markerComponent.drawNow();
|
|
}
|
|
}
|
|
|
|
super.draw();
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* All markers are removed from the display list.
|
|
*/
|
|
protected function removeAllMarkers():void
|
|
{
|
|
var markerCount:int = this.markers.length;
|
|
for(var i:int = 0; i < markerCount; i++)
|
|
{
|
|
var marker:ISeriesItemRenderer = this.markers.pop() as ISeriesItemRenderer;
|
|
marker.removeEventListener(MouseEvent.ROLL_OVER, markerRollOverHandler);
|
|
marker.removeEventListener(MouseEvent.ROLL_OUT, markerRollOutHandler);
|
|
marker.removeEventListener(MouseEvent.CLICK, markerClickHandler);
|
|
marker.removeEventListener(MouseEvent.DOUBLE_CLICK, markerDoubleClickHandler);
|
|
this.removeChild(DisplayObject(marker));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Add or remove markers as needed. current markers will be reused.
|
|
*/
|
|
protected function refreshMarkers():void
|
|
{
|
|
var itemCount:int = this.length;
|
|
var difference:int = itemCount - this.markers.length;
|
|
if(difference > 0)
|
|
{
|
|
for(var i:int = 0; i < difference; i++)
|
|
{
|
|
var marker:ISeriesItemRenderer = new this.itemRenderer();
|
|
marker.series = this;
|
|
InteractiveObject(marker).doubleClickEnabled = true;
|
|
marker.addEventListener(MouseEvent.ROLL_OVER, markerRollOverHandler, false, 0, true);
|
|
marker.addEventListener(MouseEvent.ROLL_OUT, markerRollOutHandler, false, 0, true);
|
|
marker.addEventListener(MouseEvent.CLICK, markerClickHandler, false, 0, true);
|
|
marker.addEventListener(MouseEvent.DOUBLE_CLICK, markerDoubleClickHandler, false, 0, true);
|
|
this.addChild(DisplayObject(marker));
|
|
this.markers.push(marker);
|
|
this.invalidateMarker(marker);
|
|
}
|
|
}
|
|
else if(difference < 0)
|
|
{
|
|
difference = Math.abs(difference);
|
|
for(i = 0; i < difference; i++)
|
|
{
|
|
marker = this.markers.pop() as ISeriesItemRenderer;
|
|
this.validateMarker(marker);
|
|
marker.removeEventListener(MouseEvent.ROLL_OVER, markerRollOverHandler);
|
|
marker.removeEventListener(MouseEvent.ROLL_OUT, markerRollOutHandler);
|
|
marker.removeEventListener(MouseEvent.CLICK, markerClickHandler);
|
|
marker.removeEventListener(MouseEvent.DOUBLE_CLICK, markerDoubleClickHandler);
|
|
this.removeChild(DisplayObject(marker));
|
|
}
|
|
}
|
|
|
|
var markerCount:int = this.markers.length;
|
|
for(i = 0; i < markerCount; i++)
|
|
{
|
|
marker = ISeriesItemRenderer(this.markers[i]);
|
|
marker.data = this.dataProvider[i];
|
|
DisplayObject(marker).alpha = this.getStyleValue("markerAlpha") as Number;
|
|
this.copyStylesToChild(UIComponent(marker), RENDERER_STYLES);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates whether special considerations should be taken for a newly created marker.
|
|
*/
|
|
protected function isMarkerInvalid(marker:ISeriesItemRenderer):Boolean
|
|
{
|
|
return this.markerInvalidHash[marker];
|
|
}
|
|
|
|
/**
|
|
* Invalidates a marker (considered new).
|
|
*/
|
|
protected function invalidateMarker(marker:ISeriesItemRenderer):void
|
|
{
|
|
markerInvalidHash[marker] = true;
|
|
DisplayObject(marker).visible = false;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* We never want the series to callLater after invalidating.
|
|
* The chart will ALWAYS handle drawing.
|
|
*/
|
|
override public function invalidate(property:String = InvalidationType.ALL, callLater:Boolean = true):void
|
|
{
|
|
//never call later!
|
|
super.invalidate(property, false);
|
|
}
|
|
|
|
/**
|
|
* Makes a marker valid. To be used by subclasses.
|
|
*/
|
|
protected function validateMarker(marker:ISeriesItemRenderer):void
|
|
{
|
|
DisplayObject(marker).visible = true;
|
|
delete markerInvalidHash[marker];
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Notify the parent chart that the user's mouse is over this marker.
|
|
*/
|
|
protected function markerRollOverHandler(event:MouseEvent):void
|
|
{
|
|
var itemRenderer:ISeriesItemRenderer = ISeriesItemRenderer(event.currentTarget);
|
|
var index:int = this.itemRendererToIndex(itemRenderer);
|
|
var item:Object = this.dataProvider[index];
|
|
var rollOver:ChartEvent = new ChartEvent(ChartEvent.ITEM_ROLL_OVER, index, item, itemRenderer, this);
|
|
this.dispatchEvent(rollOver);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Notify the parent chart that the user's mouse has left this marker.
|
|
*/
|
|
protected function markerRollOutHandler(event:MouseEvent):void
|
|
{
|
|
var itemRenderer:ISeriesItemRenderer = ISeriesItemRenderer(event.currentTarget);
|
|
var index:int = this.itemRendererToIndex(itemRenderer);
|
|
var item:Object = this.dataProvider[index];
|
|
var rollOut:ChartEvent = new ChartEvent(ChartEvent.ITEM_ROLL_OUT, index, item, itemRenderer, this);
|
|
this.dispatchEvent(rollOut);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Notify the parent chart that the user clicked this marker.
|
|
*/
|
|
protected function markerClickHandler(event:MouseEvent):void
|
|
{
|
|
var itemRenderer:ISeriesItemRenderer = ISeriesItemRenderer(event.currentTarget);
|
|
var index:int = this.itemRendererToIndex(itemRenderer);
|
|
var item:Object = this.dataProvider[index];
|
|
var click:ChartEvent = new ChartEvent(ChartEvent.ITEM_CLICK, index, item, itemRenderer, this);
|
|
this.dispatchEvent(click);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Notify the parent chart that the user double-clicked this marker.
|
|
*/
|
|
protected function markerDoubleClickHandler(event:MouseEvent):void
|
|
{
|
|
var itemRenderer:ISeriesItemRenderer = ISeriesItemRenderer(event.currentTarget);
|
|
var index:int = this.itemRendererToIndex(itemRenderer);
|
|
var item:Object = this.dataProvider[index];
|
|
var doubleClick:ChartEvent = new ChartEvent(ChartEvent.ITEM_DOUBLE_CLICK, index, item, itemRenderer, this);
|
|
this.dispatchEvent(doubleClick);
|
|
}
|
|
}
|
|
} |