2020-10-20 00:58:15 +02:00

372 lines
9.9 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.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.
*
* <p>Important: In the PieChart, a series uses multiple colors. The <code>seriesColors</code>
* 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.</p>
*
* @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;
}
}
}