first commit
This commit is contained in:
536
com/yahoo/astra/fl/charts/series/Series.as
Executable file
536
com/yahoo/astra/fl/charts/series/Series.as
Executable file
@ -0,0 +1,536 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user