/* 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.axes.IAxis; import com.yahoo.astra.fl.charts.axes.IRadialAxisRenderer; import com.yahoo.astra.fl.charts.axes.NumericAxis; import com.yahoo.astra.fl.charts.axes.RadialAxisRenderer; import com.yahoo.astra.fl.charts.series.ISeries; import com.yahoo.astra.fl.charts.series.PieSeries; import com.yahoo.astra.utils.GeomUtil; import com.yahoo.astra.utils.NumberUtil; import fl.core.InvalidationType; import fl.core.UIComponent; import flash.display.DisplayObject; import flash.geom.Point; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; /** * An Array containing 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. * *

Important: In the PieChart, a series uses multiple colors. The seriesColors * style is designed to work with multiple series where the index in the Array * corresponds to the series index. As a result, to set the colors on a PieChart, * an Array of color values should appear at each index in the outer Array.

* * @default [ [ 0xfcaf3e, 0x73d216, 0x729fcf, 0xfce94f, 0xad7fa8, 0x3465a4 ], [ 0x3465a4, 0xad7fa8, 0xfce94f, 0x729fcf, 0x73d216, 0xfcaf3e ] ] */ [Style(name="seriesColors", type="Array")] /** * A chart that displays its data points with pie-like wedges. * * @author Josh Tynjala */ public class PieChart extends Chart implements ICategoryChart { //-------------------------------------- // Class Variables //-------------------------------------- /** * @private */ private static var defaultStyles:Object = { seriesColors: [], seriesMarkerSkins: [] }; /** * @private * The chart styles that correspond to styles on each series. */ private static const PIE_SERIES_STYLES:Object = { markerSkins: "seriesMarkerSkins", fillColors: "seriesColors" }; //-------------------------------------- // Class Methods //-------------------------------------- /** * @private * @copy fl.core.UIComponent#getStyleDefinition() */ public static function getStyleDefinition():Object { return mergeStyles(defaultStyles, Chart.getStyleDefinition()); } //-------------------------------------- // Constructor //-------------------------------------- /** * Constructor. */ public function PieChart() { super(); this.defaultSeriesType = PieSeries; } //-------------------------------------- // Properties //-------------------------------------- /** * @private * Displays a message in live preview mode when there is no dataProvider. */ protected var livePreviewMessage:TextField; /** * @private * Storage for the dataField property. */ private var _dataField:String; /** * The field used to access data for this series. */ public function get dataField():String { return this._dataField; } /** * @private */ public function set dataField(value:String):void { if(this._dataField != value) { this._dataField = value; this.invalidate(); } } /** * @private * Storage for the categoryField property. */ private var _categoryField:String; /** * The field used to access categories for this series. */ public function get categoryField():String { return this._categoryField; } /** * @private */ public function set categoryField(value:String):void { if(this._categoryField != value) { this._categoryField = value; this.invalidate(); } } /** * @private * Flag indicating that the category names have been set manually, * and they should not be auto-generated by the chart. */ private var _categoryNamesSetByUser:Boolean = false; /** * @private * Storage for the categoryNames property. */ private var _categoryNames:Array; [Inspectable] /** * The names of the categories used by each series. */ public function get categoryNames():Array { return this._categoryNames; } /** * @private */ public function set categoryNames(value:Array):void { if(this._categoryNames != value) { this._categoryNames = value; this._categoryNamesSetByUser = value != null; this.invalidate(); } } //-------------------------------------- // Public Methods //-------------------------------------- /** * Determines the data field used by the series. Either the chart's * default data field or a data field explicitly defined for the series. */ public function seriesToDataField(series:PieSeries):String { var field:String = this.dataField; if(series.dataField) { field = series.dataField; } return field; } /** * Determines the category field used by the series. Either the chart's * default category field or a category field explicitly defined for the * series. */ public function seriesToCategoryField(series:PieSeries):String { var field:String = this.categoryField; if(series.categoryField) { field = series.categoryField; } return field; } //-------------------------------------- // Protected Methods //-------------------------------------- /** * @private */ override protected function configUI():void { super.configUI(); if(this.isLivePreview) { //special case for live previews with no data. this.livePreviewMessage = new TextField(); this.livePreviewMessage.autoSize = TextFieldAutoSize.LEFT; this.livePreviewMessage.defaultTextFormat = this.getStyleValue("textFormat") as TextFormat; this.livePreviewMessage.text = "No live preview data"; this.addChild(this.livePreviewMessage); } } /** * @private */ override protected function draw():void { if(this.isLivePreview) { //special case for live previews with no data. this.livePreviewMessage.visible = !this.dataProvider || this.dataProvider.length == 0; this.livePreviewMessage.x = (this.width - this.livePreviewMessage.width) / 2; this.livePreviewMessage.y = (this.height - this.livePreviewMessage.height) / 2; } this.generateCategories(this.series); var stylesInvalid:Boolean = this.isInvalid(InvalidationType.STYLES); super.draw(); var contentPadding:Number = this.getStyleValue("contentPadding") as Number; var seriesWidth:Number = this.width - 2 * contentPadding; var seriesHeight:Number = this.height - 2 * contentPadding; this.drawSeries(contentPadding, seriesWidth, seriesHeight); this.updateLegend(); } /** * @private * Positions and sizes each series. */ protected function drawSeries(contentPadding:Number, width:Number, height:Number):void { this.content.x = this.content.y = contentPadding; var seriesCount:int = this.series.length; for(var i:int = 0; i < seriesCount; i++) { var series:UIComponent = this.series[i] as UIComponent; this.copyStylesToSeries(ISeries(series), PIE_SERIES_STYLES); PieSeries(series).categoryNames = this.categoryNames; //if a pie chart contains more than one series, each additional series should be //a little bit smaller so that they can all be visible to the viewer. series.width = width - i * width / seriesCount; series.height = height - i * height / seriesCount; //reposition the series based on the calculated dimensions series.x = (width - series.width) / 2; series.y = (height - series.height) / 2; series.drawNow(); } } /** * @inheritDoc */ override protected function defaultDataTipFunction(item:Object, index:int, series:ISeries):String { //get the series display name first var text:String = super.defaultDataTipFunction(item, index, series); if(text.length > 0) text += "\n"; var pieSeries:PieSeries = PieSeries(series); var category:String = pieSeries.itemToCategory(item, index); text += category + "\n"; var data:Number = pieSeries.itemToData(item); text += data + "\n"; var percentage:Number = pieSeries.itemToPercentage(item); text += (percentage < 0.01 ? "< 0.01" : NumberUtil.roundToPrecision(percentage, 2)) + "%"; return text; } //-------------------------------------- // Private Methods //-------------------------------------- /** * @private * Analyzes the data to determine the category names. */ private function generateCategories(data:Array):void { if(this._categoryNamesSetByUser) { return; } //auto-detect the category labels var maxSeriesLength:int = 0; var seriesCount:int = data.length; var uniqueCategoryValues:Array = []; for(var i:int = 0; i < seriesCount; i++) { var series:PieSeries = data[i] as PieSeries; var seriesLength:int = series.length; maxSeriesLength = Math.max(seriesLength, maxSeriesLength); // determine the field for this axis var currentCategoryField:String = this.seriesToCategoryField(series); for(var j:int = 0; j < seriesLength; j++) { var item:Object = series.dataProvider[j]; var category:String = j.toString(); if(item.hasOwnProperty(currentCategoryField)) { category = item[currentCategoryField]; } if(uniqueCategoryValues.indexOf(category) < 0) { uniqueCategoryValues.push(category); } } } this._categoryNames = uniqueCategoryValues; } } }