package { import com.adobe.serialization.json.JSON; import com.yahoo.astra.fl.charts.*; import com.yahoo.astra.fl.charts.events.ChartEvent; import com.yahoo.astra.fl.charts.legend.Legend; import com.yahoo.astra.fl.charts.series.*; import com.yahoo.astra.fl.charts.skins.*; import com.yahoo.astra.fl.utils.UIComponentUtil; import com.yahoo.astra.utils.InstanceFactory; import com.yahoo.astra.utils.JavaScriptUtil; import com.yahoo.yui.LoggerCategory; import com.yahoo.yui.YUIAdapter; import com.yahoo.yui.charts.*; import fl.core.UIComponent; import flash.display.DisplayObject; import flash.display.Shape; import flash.display.Sprite; import flash.events.ErrorEvent; import flash.events.MouseEvent; import flash.external.ExternalInterface; import flash.text.TextFormat; import flash.utils.getDefinitionByName; import flash.utils.getQualifiedClassName; [SWF(backgroundColor=0xffffff)] /** * A wrapper for the Astra Charts components to allow them to be used by the YUI library. * * @author Josh Tynjala */ public class Charts extends YUIAdapter { //-------------------------------------- // Constructor //-------------------------------------- /** * Constructor. */ public function Charts() { super(); } //-------------------------------------- // Properties //-------------------------------------- /** * @private * A reference to the chart instance. */ protected var chart:Chart; /** * @private * The type of the chart specified by setType(). */ protected var type:String; /** * @private * A reference to the legend instance. */ protected var legend:Legend; /** * @private * Storage for the legendDisplay property. */ protected var _legendDisplay:String = "none"; /** * @private * Specifies the location of the legend, or "none" if the legend * is not to be displayed. */ public function get legendDisplay():String { return this._legendDisplay; } /** * @private */ public function set legendDisplay(value:String):void { this._legendDisplay = value; this.refreshComponentSize(); } /** * @private * Storage for the spacing property. */ protected var _spacing:Number = 6; /** * @private * The spacing between the chart and other objects, such as the legend. */ public function get spacing():Number { return this._spacing; } /** * @private */ public function set spacing(value:Number):void { this._spacing = value; this.refreshComponentSize(); } /** * @private * Storage for the padding property. */ protected var _padding:Number = 10; /** * @private * The padding around the chart, in pixels. */ public function get padding():Number { return this._padding; } /** * @private */ public function set padding(value:Number):void { this._padding = value; } /** * @private */ protected var backgroundAndBorder:BackgroundAndBorder; /** * @private */ override protected function get component():DisplayObject { //why do I have to do this? it's not ambiguous! return super.component; } /** * @private */ override protected function set component(value:DisplayObject):void { this.chart = Chart(value); super.component = value; } //-------------------------------------- // Public Methods //-------------------------------------- /** * Creates a chart instance based on the specified type. */ public function setType(value:String):void { if(this.chart) { this.removeChild(this.chart); this.chart.removeEventListener(ChartEvent.ITEM_CLICK, chartItemEventHandler); this.chart.removeEventListener(ChartEvent.ITEM_DOUBLE_CLICK, chartItemEventHandler); this.chart.removeEventListener(ChartEvent.ITEM_ROLL_OUT, chartItemEventHandler); this.chart.removeEventListener(ChartEvent.ITEM_ROLL_OVER, chartItemEventHandler); this.chart.removeEventListener(MouseEvent.MOUSE_DOWN, chartItemExtraEventHandler); } this.type = value; var ChartType:Class = ChartSerializer.getType(this.type); var chart:Chart = new ChartType(); chart.setStyle("contentPadding", 0); chart.setStyle("backgroundSkin", Sprite); var backgroundFactory:InstanceFactory = this.createBorderBackgroundFactory(); backgroundFactory.properties.fillColor = 0xffffff; backgroundFactory.properties.fillAlpha = 0.9; backgroundFactory.properties.borderWeight = 1; backgroundFactory.properties.borderColor = 0x000000; chart.setStyle("dataTipBackgroundSkin", backgroundFactory); chart.setStyle("seriesMarkerSkins", []); this.addChildAt(chart, 1); this.component = chart; this.chart.addEventListener(ChartEvent.ITEM_CLICK, chartItemEventHandler, false, 0, true); this.chart.addEventListener(ChartEvent.ITEM_DOUBLE_CLICK, chartItemEventHandler, false, 0, true); this.chart.addEventListener(ChartEvent.ITEM_ROLL_OUT, chartItemEventHandler, false, 0, true); this.chart.addEventListener(ChartEvent.ITEM_ROLL_OVER, chartItemEventHandler, false, 0, true); this.chart.addEventListener(MouseEvent.MOUSE_DOWN, chartItemExtraEventHandler, false, 0, true); this.chart.legend = this.legend; this.log("Type set to \"" + this.type + "\""); } public function setDataProvider(value:Array):void { var dataProvider:Array = []; var seriesCount:int = value.length; var seriesStyles:Array = []; for(var i:int = 0; i < seriesCount; i++) { var dataFromJavaScript:Object = value[i]; var currentData:ISeries = this.chart.dataProvider[i] as ISeries; var seriesType:Class = SeriesSerializer.shortNameToSeriesType(dataFromJavaScript.type ? dataFromJavaScript.type : this.type); var series:ISeries; if(currentData && getDefinitionByName(getQualifiedClassName(currentData)) == seriesType) { //reuse the series if possible because we want animation series = SeriesSerializer.readSeries(dataFromJavaScript, currentData); } else { series = SeriesSerializer.readSeries(dataFromJavaScript); } dataProvider[i] = series; //this is where we parse the individual series styles, and we convert them //to the format the chart actually expects. //we fill in with defaults for styles that have not been specified if(dataFromJavaScript.style) { seriesStyles.push(dataFromJavaScript.style); } else seriesStyles.push(null); } this.log("Displaying " + seriesCount + " series."); //set data provider and new styles this.chart.dataProvider = dataProvider; //make sures the series are created! this.chart.drawNow(); //set the styles for the series this.setSeriesStyles(seriesStyles); this.refreshComponentSize(); } /** * Returns the category names. */ public function getCategoryNames():Array { var categoryChart:ICategoryChart = this.chart as ICategoryChart; if(categoryChart) { return categoryChart.categoryNames; } var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Cannot find categoryNames on a chart of type " + shortName, LoggerCategory.WARN); return null; } /** * Sets the category names used if the data requires a category axis. * This field should be used if the data does not define the category * values directly. */ public function setCategoryNames(value:Array):void { var categoryChart:ICategoryChart = this.chart as ICategoryChart; if(categoryChart) { categoryChart.categoryNames = value; } else { var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to set categoryNames on a chart of type " + shortName, LoggerCategory.WARN); } } /** * Returns the field used in complex objects to access data to be * displayed on the PieChart. */ public function getDataField():String { var pieChart:PieChart = this.chart as PieChart; if(pieChart) { return pieChart.dataField; } var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to find dataField on a chart of type " + shortName, LoggerCategory.WARN); return null; } /** * Sets the field used in complex objects to access data to be displayed * on the PieChart. */ public function setDataField(value:String):void { var pieChart:PieChart = this.chart as PieChart; if(pieChart) { pieChart.dataField = value; } else { var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to set dataField on a chart of type " + shortName, LoggerCategory.WARN); } } /** * Returns the field used in complex objects to access categories to be * displayed on the PieChart. */ public function getCategoryField():String { var pieChart:PieChart = this.chart as PieChart; if(pieChart) { return pieChart.categoryField; } var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to find categoryField on a chart of type " + shortName, LoggerCategory.WARN); return null; } /** * Sets the field used in complex objects to access categories to be displayed * on the PieChart. */ public function setCategoryField(value:String):void { var pieChart:PieChart = this.chart as PieChart; if(pieChart) { pieChart.categoryField = value; } else { var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to set categoryField on a chart of type " + shortName, LoggerCategory.WARN); } } /** * Returns the field used in complex objects to access data to be * displayed on the horizontal axis. */ public function getHorizontalField():String { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { return cartesianChart.horizontalField; } var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to find horizontalField on a chart of type " + shortName, LoggerCategory.WARN); return null; } /** * Sets the field used in complex objects to access data to be displayed * on the horizontal axis. If the input data is XML, and the field is an * attribute, be sure to include the "@" symbol at the beginning of the * field name. */ public function setHorizontalField(value:String):void { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { cartesianChart.horizontalField = value; } else { var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to set horizontalField on a chart of type " + shortName, LoggerCategory.WARN); } } /** * Returns the field used in complex objects to access data to be * displayed on the vertical axis. */ public function getVerticalField():String { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { return cartesianChart.verticalField; } var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to find verticalField on a chart of type " + shortName, LoggerCategory.WARN); return null; } /** * Sets the field used in complex objects to access data to be displayed * on the vertical axis. If the input data is XML, and the field is an * attribute, be sure to include the "@" symbol at the beginning of the * field name. */ public function setVerticalField(value:String):void { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { cartesianChart.verticalField = value; } else { var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to set verticalField on a chart of type " + shortName, LoggerCategory.WARN); } } /** * Returns the title displayed next to the vertical axis. */ public function getHorizontalAxisTitle():String { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { return cartesianChart.horizontalAxisTitle; } var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to find horizontalAxisTitle on a chart of type " + shortName, LoggerCategory.WARN); return null; } /** * Sets the title displayed next to the horizontal axis. */ public function setHorizontalAxisTitle(value:String):void { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { cartesianChart.horizontalAxisTitle = value; } else { var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to set horizontalAxisTitle on a chart of type " + shortName, LoggerCategory.WARN); } } /** * Returns the title displayed next to the vertical axis. */ public function getVerticalAxisTitle():String { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { return cartesianChart.verticalAxisTitle; } var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to find verticalAxisTitle on a chart of type " + shortName, LoggerCategory.WARN); return null; } /** * Sets the title displayed next to the vertical axis. */ public function setVerticalAxisTitle(value:String):void { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { cartesianChart.verticalAxisTitle = value; } else { var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to set verticalAxisTitle on a chart of type " + shortName, LoggerCategory.WARN); } } /** * Updates the horizontal axis with a new type. */ public function setHorizontalAxis(value:Object):void { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { cartesianChart.horizontalAxis = AxisSerializer.readAxis(value); } else { var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to set horizontalAxis on a chart of type " + shortName, LoggerCategory.WARN); } } /** * Updates the vertical axis with a new type. */ public function setVerticalAxis(value:Object):void { var cartesianChart:CartesianChart = this.chart as CartesianChart; if(cartesianChart) { cartesianChart.verticalAxis = AxisSerializer.readAxis(value); } else { var shortName:String = ChartSerializer.getShortName(getQualifiedClassName(this.chart)); this.log("Unable to set verticalAxis on a chart of type " + shortName, LoggerCategory.WARN); } } /** * Sets the JavaScript function to call to generate the chart's data tip. */ public function setDataTipFunction(value:String):void { var delegate:Object = {dataTipFunction: JavaScriptUtil.createCallbackFunction(value).callback}; delegate.callback = function(item:Object, index:int, series:ISeries):String { return delegate.dataTipFunction(item, index, SeriesSerializer.writeSeries(series)); } this.chart.dataTipFunction = delegate.callback; } /** * Accepts a JSON-encoded set of styles for the chart itself. * Flash Player versions below 9.0.60 don't encode ExternalInterface * calls correctly! */ public function setStyles(styles:String):void { if(!styles) return; var parsedStyles:Object = JSON.decode(styles); for(var styleName:String in parsedStyles) { this.setStyle(styleName, parsedStyles[styleName], false); } } public function setStyle(name:String, value:Object, json:Boolean = true):void { if(json && value) { //by default, we assume it's json data, only setStyles will send false value = JSON.decode(value as String); } var needsSizingRefresh:Boolean = false; switch(name) { case "padding": this.padding = value as Number; needsSizingRefresh = true; break; case "spacing": this.spacing = value as Number; needsSizingRefresh = true; break; case "animationEnabled": this.chart.setStyle("animationEnabled", value); break; case "border": if(value.color != null) { this.backgroundAndBorder.borderColor = this.parseColor(value.color); } if(value.size != null) { this.backgroundAndBorder.borderWeight = value.size; needsSizingRefresh = true; } break; case "background": if(value.color != null) { this.backgroundAndBorder.fillColor = this.parseColor(value.color); } if(value.image) { this.backgroundAndBorder.image = value.image; } if(value.alpha != null) { this.backgroundAndBorder.fillAlpha = value.alpha; } if(value.mode) { this.backgroundAndBorder.imageMode = value.mode; } break; case "font": var textFormat:TextFormat = TextFormatSerializer.readTextFormat(value); this.chart.setStyle("textFormat", textFormat); break; case "dataTip": this.setDataTipStyles(value); break; case "xAxis": this.setAxisStyles(value, "horizontal"); break; case "yAxis": this.setAxisStyles(value, "vertical"); break; case "legend": this.setLegendStyles(value); break; default: this.log("Unknown style: " + name, LoggerCategory.WARN); } if(needsSizingRefresh) { this.refreshComponentSize(); } } public function setSeriesStyles(styles:Array):void { var defaultSeriesColors:Array = [0x00b8bf, 0x8dd5e7, 0xedff9f, 0xffa928, 0xc0fff6, 0xd00050, 0xc6c6c6, 0xc3eafb, 0xfcffad, 0xcfff83, 0x444444, 0x4d95dd, 0xb8ebff, 0x60558f, 0x737d7e, 0xa64d9a, 0x8e9a9b, 0x803e77]; //will be filled based on the defaults or the series style definition, if present. var seriesColors:Array = []; var seriesCount:int = Math.min(this.chart.dataProvider.length, styles.length); for(var i:int = 0; i < seriesCount; i++) { var series:ISeries = ISeries(this.chart.dataProvider[i]); var style:Object = styles[i]; if(style) { style = JSON.decode(style as String); } //defaults var defaultColors:Array = defaultSeriesColors.concat(); if(series is PieSeries) { defaultColors = [defaultColors]; } var defaultSize:Number = 10; if(series is ColumnSeries || series is BarSeries) { defaultSize = 20; } var defaultSkin:Object = RectangleSkin; if(series is LineSeries) { defaultSkin = CircleSkin; } else if(series is PieSeries) { defaultSkin = [defaultSkin]; } //initialize styles with defaults var color:Object = defaultColors[i % defaultColors.length]; var skin:Object = defaultSkin; var mode:Object = "repeat"; if(style) { for(var styleName:String in style) { switch(styleName) { case "images": if(!(series is PieSeries)) { this.log(styleName + " style is only supported by series of type 'pie'.", LoggerCategory.WARN); break; } var images:Array = style.images as Array; var imageCount:int = images.length; for(var j:int = 0; j < imageCount; j++) { images[j] = this.createMarkerSkin(String(images[j]), series); } skin = images; break; case "image": skin = this.createMarkerSkin(style.image, series); if(!(series is LineSeries)) { skin.properties.fillColor = color; skin.properties.fillAlpha = 1; } break; case "mode": mode = style.mode; break; case "colors": if(!(series is PieSeries)) { this.log(styleName + " style is only supported by series of type 'pie'.", LoggerCategory.WARN); break; } var colors:Array = style.colors; var colorCount:int = colors.length; for(j = 0; j < colorCount; j++) { colors[j] = this.parseColor(colors[j]); } color = colors; break; case "color": color = this.parseColor(style.color); if(skin is InstanceFactory && !(series is LineSeries)) { skin.properties.fillColor = color; skin.properties.fillAlpha = 1; } break; case "size": UIComponent(series).setStyle("markerSize", style.size); break; case "alpha": UIComponent(series).setStyle("markerAlpha", style.alpha); break; case "showAreaFill": //LineSeries only if(!(series is LineSeries)) { this.log("The style " + styleName + " is only supported by series of type 'line'.", LoggerCategory.WARN); } UIComponent(series).setStyle("showAreaFill", style.showAreaFill); break; case "areaFillAlpha": //LineSeries only if(!(series is LineSeries)) { this.log("The style " + styleName + " is only supported by series of type 'line'.", LoggerCategory.WARN); } UIComponent(series).setStyle("areaFillAlpha", style.areaFillAlpha); break; case "lineSize": //LineSeries only if(!(series is LineSeries)) { this.log("The style " + styleName + " is only supported by series of type 'line'.", LoggerCategory.WARN); } UIComponent(series).setStyle("lineWeight", style.lineSize); break; case "connectPoints": //LineSeries only if(!(series is LineSeries)) { this.log("The style " + styleName + " is only supported by series of type 'line'.", LoggerCategory.WARN); } UIComponent(series).setStyle("connectPoints", style.connectPoints); break; case "connectDiscontinuousPoints": //LineSeries only if(!(series is LineSeries)) { this.log("The style " + styleName + " is only supported by series of type 'line'.", LoggerCategory.WARN); } UIComponent(series).setStyle("connectDiscontinuousPoints", style.connectDiscontinuousPoints); break; case "discontinuousDashLength": //LineSeries only if(!(series is LineSeries)) { this.log("The style " + styleName + " is only supported by series of type 'line'.", LoggerCategory.WARN); } UIComponent(series).setStyle("discontinuousDashLength", style.discontinuousDashLength); break; case "showLabels": //PieSeries only if(!(series is PieSeries)) { this.log("The style " + styleName + " is only supported by series of type 'pie'.", LoggerCategory.WARN); } UIComponent(series).setStyle("showLabels", style.showLabels); break; case "hideOverlappingLabels": //PieSeries only if(!(series is PieSeries)) { this.log("The style " + styleName + " is only supported by series of type 'pie'.", LoggerCategory.WARN); } UIComponent(series).setStyle("hideOverlappingLabels", style.showLabels); break; case "font": //PieSeries only if(!(series is PieSeries)) { this.log("The style " + styleName + " is only supported by series of type 'pie'.", LoggerCategory.WARN); } UIComponent(series).setStyle("textFormat", TextFormatSerializer.readTextFormat(style.font)) break; default: this.log("Unknown series style: " + styleName, LoggerCategory.WARN); } } } if(mode) { if(skin is InstanceFactory) { skin.properties.imageMode = mode; } else if(skin is Array) { var skinCount:int = (skin as Array).length; for(j = 0; j < skinCount; j++) { var subSkin:InstanceFactory = skin[j] as InstanceFactory; if(subSkin) { subSkin.properties.imageMode = mode; } } } } if(series is PieSeries) { PieSeries(series).setStyle("markerSkins", skin); } else UIComponent(series).setStyle("markerSkin", skin); seriesColors[i] = color; } this.chart.setStyle("seriesColors", seriesColors); this.chart.drawNow(); } //-------------------------------------- // Protected Methods //-------------------------------------- /** * @private (protected) * Initialize the functions that may be called by JavaScript through ExternalInterface. */ override protected function initializeComponent():void { super.initializeComponent(); try { ExternalInterface.addCallback("setType", setType); ExternalInterface.addCallback("setStyle", setStyle); ExternalInterface.addCallback("setStyles", setStyles); ExternalInterface.addCallback("setSeriesStyles", setSeriesStyles); ExternalInterface.addCallback("setDataProvider", setDataProvider); ExternalInterface.addCallback("getCategoryNames", getCategoryNames); ExternalInterface.addCallback("setCategoryNames", setCategoryNames); ExternalInterface.addCallback("setDataTipFunction", setDataTipFunction); ExternalInterface.addCallback("getCategoryNames", getCategoryNames); ExternalInterface.addCallback("setCategoryNames", setCategoryNames); //CartesianChart ExternalInterface.addCallback("getHorizontalField", getHorizontalField); ExternalInterface.addCallback("setHorizontalField", setHorizontalField); ExternalInterface.addCallback("getVerticalField", getVerticalField); ExternalInterface.addCallback("setVerticalField", setVerticalField); ExternalInterface.addCallback("setHorizontalAxis", setHorizontalAxis); ExternalInterface.addCallback("setVerticalAxis", setVerticalAxis); //PieChart ExternalInterface.addCallback("getDataField", getDataField); ExternalInterface.addCallback("setDataField", setDataField); ExternalInterface.addCallback("getCategoryField", getCategoryField); ExternalInterface.addCallback("setCategoryField", setCategoryField); } catch(error:SecurityError) { //do nothing. it will be caught by the YUIAdapter. } this.backgroundAndBorder = new BackgroundAndBorder(); this.backgroundAndBorder.width = this.stage.stageWidth; this.backgroundAndBorder.height = this.stage.stageHeight; this.backgroundAndBorder.addEventListener(ErrorEvent.ERROR, backgroundErrorHandler); this.addChild(this.backgroundAndBorder); this.legend = new Legend(); this.legend.setStyle("backgroundSkin", Shape); this.addChild(this.legend); } /** * @private (protected) * Since Chart is a Flash CS3 component, we should call drawNow() to be sure it updates properly. */ override protected function refreshComponentSize():void { super.refreshComponentSize(); if(this.backgroundAndBorder) { this.backgroundAndBorder.width = this.stage.stageWidth; this.backgroundAndBorder.height = this.stage.stageHeight; this.backgroundAndBorder.drawNow(); } if(this.chart) { this.chart.x = this.chart.y = this.backgroundAndBorder.borderWeight + this.padding; this.chart.width -= 2 * (this.backgroundAndBorder.borderWeight + this.padding); this.chart.height -= 2 * (this.backgroundAndBorder.borderWeight + this.padding); if(this.legend && this.legendDisplay != "none") { this.legend.visible = true; //we need to draw because the legend resizes itself this.legend.drawNow(); if(this.legendDisplay == "left" || this.legendDisplay == "right") { if(this.legendDisplay == "left") { this.legend.x = this.backgroundAndBorder.borderWeight + this.padding; this.chart.x = this.legend.x + this.legend.width + this.spacing; } else //right { this.legend.x = this.stage.stageWidth - this.backgroundAndBorder.borderWeight - this.legend.width - padding; } //center vertically this.legend.y = Math.max(0, (this.stage.stageHeight - this.legend.height) / 2); this.chart.width -= (this.legend.width + this.spacing); } else //top or bottom { if(this.legendDisplay == "top") { this.legend.y = this.backgroundAndBorder.borderWeight + this.padding; this.chart.y = this.legend.y + this.legend.height + this.spacing; } else //bottom { this.legend.y = this.stage.stageHeight - this.backgroundAndBorder.borderWeight - this.legend.height - padding; } //center horizontally this.legend.x = Math.max(0, (this.stage.stageWidth - this.legend.width) / 2); this.chart.height -= (this.legend.height + this.spacing); } //we disable animation temporarily because we don't want the markers //sliding around because the legend resized if(this.legend && this.legendDisplay != "none") { var oldAnimationEnabled:Boolean = UIComponentUtil.getStyleValue(this.chart, "animationEnabled"); this.chart.setStyle("animationEnabled", false); this.chart.drawNow(); this.chart.setStyle("animationEnabled", oldAnimationEnabled); } } else { this.legend.visible = false; } this.chart.drawNow(); } } /** * @private (protected) * Logs errors for the background image loading. */ protected function backgroundErrorHandler(event:ErrorEvent):void { this.log(event.text, LoggerCategory.ERROR); } /** * @private (protected) * * Receives chart item mouse events and passes them out to JavaScript. */ protected function chartItemEventHandler(event:ChartEvent):void { var type:String = event.type.replace("Roll", "Mouse"); type += "Event"; var seriesIndex:int = (this.chart.dataProvider as Array).indexOf(event.series); var itemEvent:Object = {type: type, seriesIndex: seriesIndex, index: event.index, item: event.item, x: this.mouseX, y: this.mouseY}; this.dispatchEventToJavaScript(itemEvent); } private var _lastMouseItem:ISeriesItemRenderer; protected function chartItemExtraEventHandler(event:MouseEvent):void { var dragEventType:String = "itemDragEvent"; var renderer:ISeriesItemRenderer = this._lastMouseItem; this._lastMouseItem = null; if(event.type == MouseEvent.MOUSE_DOWN) { //crawl up until we get to the chart or an item renderer var displayObject:DisplayObject = event.target as DisplayObject; while(!(displayObject is ISeriesItemRenderer) && !(displayObject is Chart)) { displayObject = displayObject.parent; } if(displayObject is ISeriesItemRenderer) { renderer = ISeriesItemRenderer(displayObject); this.stage.addEventListener(MouseEvent.MOUSE_MOVE, chartItemExtraEventHandler); this.stage.addEventListener(MouseEvent.MOUSE_UP, chartItemExtraEventHandler); } else { renderer = null; } dragEventType = "itemDragStartEvent"; } else if(event.type == MouseEvent.MOUSE_UP) { dragEventType = "itemDragEndEvent"; this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, chartItemExtraEventHandler); this.stage.removeEventListener(MouseEvent.MOUSE_UP, chartItemExtraEventHandler); } //if we've found an item renderer, dispatch the event if(renderer is ISeriesItemRenderer) { var seriesIndex:int = (this.chart.dataProvider as Array).indexOf(renderer.series); var itemIndex:int = renderer.series.dataProvider.indexOf(renderer.data) var itemEvent:Object = {type: dragEventType, seriesIndex: seriesIndex, index: itemIndex, item: renderer.data, x: this.mouseX, y: this.mouseY}; this.dispatchEventToJavaScript(itemEvent); this._lastMouseItem = renderer; } } protected function setDataTipStyles(styles:Object):void { var contentPadding:Number = 6; if(styles.padding >= 0) { contentPadding = styles.padding; } if(styles.border || styles.background) { //defaults var backgroundFactory:InstanceFactory = this.createBorderBackgroundFactory(); backgroundFactory.properties.fillColor = 0xffffff; backgroundFactory.properties.fillAlpha = 0.9; backgroundFactory.properties.borderWeight = 1; backgroundFactory.properties.borderColor = 0x000000; var border:Object = styles.border; if(border) { if(border.color != null) { backgroundFactory.properties.borderColor = this.parseColor(border.color) } if(border.size != null) { backgroundFactory.properties.borderWeight = border.size; contentPadding += border.size; } } var background:Object = styles.background; if(background) { if(background.color != null) { backgroundFactory.properties.fillColor = this.parseColor(background.color); } if(background.image) { backgroundFactory.properties.image = background.image; } if(background.alpha != null) { backgroundFactory.properties.fillAlpha = background.alpha; } if(background.mode) { backgroundFactory.properties.imageMode = background.mode; } } this.chart.setStyle("dataTipBackgroundSkin", backgroundFactory); } this.chart.setStyle("dataTipContentPadding", contentPadding); if(styles.font) { var textFormat:TextFormat = TextFormatSerializer.readTextFormat(styles.font); this.chart.setStyle("dataTipTextFormat", textFormat); } } protected function setAxisStyles(styles:Object, axisName:String):void { if(styles.color != null) { this.chart.setStyle(axisName + "AxisColor", this.parseColor(styles.color)); } if(styles.size != null) { this.chart.setStyle(axisName + "AxisWeight", styles.size); } if(styles.showLabels != null) { this.chart.setStyle("show" + axisName.substr(0, 1).toUpperCase() + axisName.substr(1) + "AxisLabels", styles.showLabels); } if(styles.hideOverlappingLabels != null) { this.chart.setStyle(axisName.substr(0, 1).toUpperCase() + axisName.substr(1) + "AxisHideOverlappingLabels", styles.hideOverlappingLabels); } if(styles.majorGridLines) { var majorGridLines:Object = styles.majorGridLines; if(majorGridLines.color != null) { this.chart.setStyle(axisName + "AxisGridLineColor", this.parseColor(majorGridLines.color)); } if(majorGridLines.size != null) { this.chart.setStyle(axisName + "AxisGridLineWeight", majorGridLines.size); this.chart.setStyle("show" + axisName.substr(0, 1).toUpperCase() + axisName.substr(1) + "AxisGridLines", majorGridLines.size > 0); } } if(styles.minorGridLines) { var minorGridLines:Object = styles.minorGridLines; if(minorGridLines.color != null) { this.chart.setStyle(axisName + "AxisMinorGridLineColor", this.parseColor(minorGridLines.color)); } if(minorGridLines.size != null) { this.chart.setStyle(axisName + "AxisMinorGridLineWeight", minorGridLines.size); this.chart.setStyle("show" + axisName.substr(0, 1).toUpperCase() + axisName.substr(1) + "AxisMinorGridLines", minorGridLines.size > 0); } } if(styles.majorTicks) { var majorTicks:Object = styles.majorTicks; if(majorTicks.color != null) { this.chart.setStyle(axisName + "AxisTickColor", this.parseColor(majorTicks.color)); } if(majorTicks.size != null) { this.chart.setStyle(axisName + "AxisTickWeight", majorTicks.size); } if(majorTicks.length != null) { this.chart.setStyle(axisName + "AxisTickLength", majorTicks.length); } if(majorTicks.display) { this.chart.setStyle("show" + axisName.substr(0, 1).toUpperCase() + axisName.substr(1) + "AxisTicks", majorTicks.display != "none"); if(majorTicks.display != "none") { this.chart.setStyle(axisName + "AxisTickPosition", majorTicks.display); } } } if(styles.minorTicks) { var minorTicks:Object = styles.minorTicks; if(minorTicks.color != null) { this.chart.setStyle(axisName + "AxisMinorTickColor", this.parseColor(minorTicks.color)); } if(minorTicks.size != null) { this.chart.setStyle(axisName + "AxisMinorTickWeight", minorTicks.size); } if(minorTicks.length != null) { this.chart.setStyle(axisName + "AxisMinorTickLength", minorTicks.length); } if(minorTicks.display) { this.chart.setStyle("show" + axisName.substr(0, 1).toUpperCase() + axisName.substr(1) + "AxisMinorTicks", minorTicks.display != "none"); if(minorTicks.display != "none") { this.chart.setStyle(axisName + "AxisMinorTickPosition", minorTicks.display); } } } } protected function setLegendStyles(styles:Object):void { if(styles.font) { var textFormat:TextFormat = TextFormatSerializer.readTextFormat(styles.font); this.legend.setStyle("textFormat", textFormat); } if(styles.spacing != null) { this.legend.setStyle("gap", styles.spacing); } if(styles.display) { switch(styles.display) { case "left": case "right": this.legend.setStyle("direction", "vertical"); break; default: //top, bottom this.legend.setStyle("direction", "horizontal"); } this.legendDisplay = styles.display; } var contentPadding:Number = 6; if(styles.padding != null) { contentPadding = styles.padding; } if(styles.border || styles.background) { var backgroundFactory:InstanceFactory = this.createBorderBackgroundFactory(); var border:Object = styles.border; if(border) { if(border.color != null) { backgroundFactory.properties.borderColor = this.parseColor(border.color) } if(border.size != null) { backgroundFactory.properties.borderWeight = border.size; contentPadding += border.size; } } var background:Object = styles.background; if(background) { if(background.color != null) { backgroundFactory.properties.fillColor = this.parseColor(background.color); } if(background.image) { backgroundFactory.properties.image = background.image; } if(background.alpha != null) { backgroundFactory.properties.fillAlpha = background.alpha; } if(background.mode) { backgroundFactory.properties.imageMode = background.mode; } } this.legend.setStyle("backgroundSkin", backgroundFactory); } this.legend.setStyle("contentPadding", contentPadding); } //-------------------------------------- // Private Methods //-------------------------------------- /** * @private * Creates a pseudo-class to instantiate a BackgroundAndBorder object. */ private function createBorderBackgroundFactory():InstanceFactory { var factory:InstanceFactory = new InstanceFactory(BackgroundAndBorder); factory.properties = { fillColor: 0xffffff, fillAlpha: 1, borderColor: 0x000000, borderWeight: 0, image: null, imageMode: BackgroundImageMode.REPEAT }; factory.methods = { addEventListener: [ErrorEvent.ERROR, backgroundLoadErrorHandler, false, 0, true] }; return factory; } private function backgroundLoadErrorHandler(event:ErrorEvent):void { this.log(event.text, LoggerCategory.ERROR); } private function createMarkerSkin(imagePath:String, series:ISeries):InstanceFactory { //a simple UILoader would be enough, but we want tiling var skin:InstanceFactory = this.createBorderBackgroundFactory(); //turn off the border skin.properties.borderWeight = 0; //turn off the fill skin.properties.fillAlpha = 0; skin.properties.image = imagePath; skin.properties.imageMode = BackgroundImageMode.REPEAT; if(series is LineSeries) { //make points fit to size and maintain their aspect ratio skin.properties.imageMode = BackgroundImageMode.STRETCH_AND_MAINTAIN_ASPECT_RATIO; } return skin; } private function parseColor(value:Object):uint { if(!(value is Number)) { var valueAsString:String = value.toString().replace("#", ""); if(valueAsString.indexOf("0x") != 0) { valueAsString = "0x" + valueAsString; } return parseInt(String(valueAsString), 16); } return uint(value); } } }