flex 3 cookbook sample chapter

26
CHAPTER 16 Charting The Flex Charting Framework is a powerful series of data-rich controls that let you create deep and interactive displays of many types of data. Flex defines a few of the most commonly used charts—bar charts, pie charts, and column charts—and gives you a great deal of control over their appearance. A Chart contains a ChartSeries that creates the item renderers based on the dataProvider passed to the Chart. The ChartS eries object can use an itemRenderer, have distinct dataFields from the dataProvider used to display data in that ChartSeries, use display effects, and have distinct properties set to handle user interaction. The Flex Charting Framework is an immense topic, and as such the recipes in this chapter can cover only a small set of the possibilities. It should also be noted that the Flex Charting components are a part of the Flex data visualization package, which is included in the Flex professional edition and not in the standard edition nor with the free Flex 3 SDK. 16.1 Create a Chart Problem You want to add a chart to your application. Solution Create a chart of the desired type and add the appropriate ChartSeries object for that ChartType, and then add a dataProvider to the ChartSeries. Discussion Charts come in a variety of types, and it’s important to know what you have in your toolbox before starting to work. For every chart type, there is a corresponding ChartSeries. To add a series of data visualized as a particular type, you add the corresponding series type and bind it to a dataProvider. The ChartSeries defines what data is shown on the x- and y-axis and 457

Upload: guest35ce95

Post on 17-May-2015

1.557 views

Category:

Technology


6 download

DESCRIPTION

Flex 3 Cookbook Sample Chapter

TRANSCRIPT

Page 1: Flex 3 Cookbook Sample Chapter

CHAPTER 16

Charting

The Flex Charting Framework is a powerful series of data-rich controls that let youcreate deep and interactive displays of many types of data. Flex defines a few of themost commonly used charts—bar charts, pie charts, and column charts—and givesyou a great deal of control over their appearance. A Chart contains a ChartSeries that creates the item renderers based on the dataProvider passed to the Chart. The ChartSeries object can use an itemRenderer, have distinct dataFields from the dataProviderused to display data in that ChartSeries, use display effects, and have distinct propertiesset to handle user interaction. The Flex Charting Framework is an immense topic, andas such the recipes in this chapter can cover only a small set of the possibilities. It shouldalso be noted that the Flex Charting components are a part of the Flex data visualizationpackage, which is included in the Flex professional edition and not in the standardedition nor with the free Flex 3 SDK.

16.1 Create a ChartProblemYou want to add a chart to your application.

SolutionCreate a chart of the desired type and add the appropriate ChartSeries object for thatChartType, and then add a dataProvider to the ChartSeries.

DiscussionCharts come in a variety of types, and it’s important to know what you have in yourtoolbox before starting to work.

For every chart type, there is a corresponding ChartSeries. To add a series of datavisualized as a particular type, you add the corresponding series type and bind it to adataProvider. The ChartSeries defines what data is shown on the x- and y-axis and

457

Page 2: Flex 3 Cookbook Sample Chapter

what the series name is. Series can also have filters applied to give drop shadows, blur,or glow.

Depending on the form of your data, you may need to define a horizontal or verticalaxis. If your data is grouped into categories, such as dates, countries, or people, you’llneed a CategoryAxis. If you are using numeric data, use a LinearAxis.

The dataProvider of a chart can be an array or collection of objects; it can also be anXMLList object. If you set a dataProvider in your chart tag, your series objects will inheritthat dataProvider, or you can specify a dataProvider for each series object. Differentseries objects can use different dataProviders. A chart does not have to use all the datain the dataProvider; it can use only a specified range.

To create bar and pie charts, you would use code similar to this:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" backgroundColor="0xFFFFFF"> <mx:Script> <![CDATA[

// a basic data set

[Bindable] public var chartDP:Array = [ {day:'Monday',rainfall:10,elevation:100,temperature:78}, {day:'Tuesday',rainfall:7,elevation:220,temperature:66}, {day:'Wednesday',rainfall:5,elevation:540,temperature:55}, {day:'Thursday',rainfall:8,elevation:60,temperature:84}, {day:'Friday',rainfall:11,elevation:390,temperature:52}, {day:'Saturday',rainfall:12,elevation:790,temperature:45}, {day:'Sunday',rainfall:14,elevation:1220,temperature:24} ];

]]> </mx:Script> <mx:ToggleButtonBar dataProvider="{simpleCharts}" direction="vertical" /> <mx:ViewStack id="simpleCharts" >

<mx:Canvas label="Bar"> <mx:BarChart dataProvider="{chartDP}" > <mx:verticalAxis> <mx:CategoryAxis dataProvider="{chartDP}" categoryField="day" /> </mx:verticalAxis> <mx:series>

<!-- bar chart uses a BarSeries -->

<mx:BarSeries yField="day" xField="rainfall" displayName="day" /> </mx:series> </mx:BarChart>

458 | Chapter 16: Charting

Page 3: Flex 3 Cookbook Sample Chapter

</mx:Canvas>

<mx:Canvas label="Pie"> <mx:PieChart dataProvider="{chartDP}" > <!-- no axes need to be defined in a pie chart -->

<mx:series>

<!-- pie chart uses a pie series -->

<mx:PieSeries field="rainfall" nameField="day" labelPosition="callout" displayName="rainfall" /> </mx:series> </mx:PieChart> </mx:Canvas>

</mx:ViewStack></mx:Application>

The Candlestick and the HighLowOpenClose chart types need a slightly different kindof data set:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" backgroundColor="0xFFFFFF"> <mx:Script> <![CDATA[ // the field names don't need to be 'high','open','low', and 'close', butyou need four different fields to get this kind of chart to work [Bindable] public var highLowChartDP:Array = [ {date:"1-Aug-05",open:42.57,high:43.08,low:42.08,close:42.75}, {date:"2-Aug-05",open:42.89,high:43.5,low:42.61,close:43.19}, {date:"3-Aug-05",open:43.19,high:43.31,low:42.77,close:43.22}, {date:"4-Aug-05",open:42.89,high:43,low:42.29,close:42.71}, {date:"5-Aug-05",open:42.49,high:43.36,low:42.02,close:42.99}, {date:"8-Aug-05",open:43,high:43.25,low:42.61,close:42.65}, {date:"9-Aug-05",open:42.93,high:43.89,low:42.91,close:43.82}, {date:"10-Aug-05",open:44,high:44.39,low:43.31,close:43.38}, {date:"11-Aug-05",open:43.39,high:44.12,low:43.25,close:44}, {date:"12-Aug-05",open:43.46,high:46.22,low:43.36,close:46.1} ]; ]]> </mx:Script> <mx:CandlestickChart dataProvider="{highLowChartDP}" showDataTips="true"> <mx:verticalAxis> <mx:LinearAxis minimum="40" maximum="50" /> </mx:verticalAxis> <mx:horizontalAxis> <mx:CategoryAxis categoryField="date" /> </mx:horizontalAxis> <mx:series> <mx:CandlestickSeries

16.1 Create a Chart | 459

Page 4: Flex 3 Cookbook Sample Chapter

dataProvider="{highLowChartDP}" openField="open" highField="high" lowField="low" closeField="close" displayName="Rainfall"/> </mx:series> </mx:CandlestickChart></mx:Application>

16.2 Add Effects to ChartsProblemYou want to use effects for a chart.

SolutionAdd an effect of the desired type to the chart’s axis or data series objects by using<mx:rollOverEffect> or <mx:rollOutEffect> within the definition of the Axis.

DiscussionAny Effect from the mx.effects package can be added to the Series or Axis of aChart. A simple rollover effect can enhance the look and feel of your chart. Here’s abasic example of how to add a fade-in and fade-out effect on rollover and rollout ofchart elements:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" backgroundColor="0xFFFFFF"> <mx:Script> <![CDATA[ [Bindable] public var chartDP:Array = [ {day:'Monday',rainfall:10,elevation:100,temperature:78}, {day:'Tuesday',rainfall:7,elevation:220,temperature:66}, {day:'Wednesday',rainfall:5,elevation:540,temperature:55}, {day:'Thursday',rainfall:8,elevation:60,temperature:84}, {day:'Friday',rainfall:11,elevation:390,temperature:52}, {day:'Saturday',rainfall:12,elevation:790,temperature:45}, {day:'Sunday',rainfall:14,elevation:1220,temperature:24} ]; ]]> </mx:Script <mx:AreaChart dataProvider="{chartDP}" > <mx:horizontalAxis> <mx:CategoryAxis dataProvider="{chartDP}" categoryField="day" /> </mx:horizontalAxis> <mx:series> <mx:AreaSeries alpha=".5"

460 | Chapter 16: Charting

Page 5: Flex 3 Cookbook Sample Chapter

yField="rainfall" displayName="rainfall">

Here a Fade effect is set for the rollover and rollout of the AreaSeries; these effects willaffect only this particular series, not the chart as a whole:

<mx:rollOverEffect> <mx:Fade alphaFrom=".5" alphaTo="1" duration="500" /> </mx:rollOverEffect> <mx:rollOutEffect> <mx:Fade alphaFrom="1" alphaTo=".5" duration="500" /> </mx:rollOutEffect> </mx:AreaSeries> </mx:series> </mx:AreaChart></mx:Application>

To add a little more action to your chart, you can use SeriesInterpolate, SeriesZoom,and SeriesSlide to animate changing data. SeriesInterpolate animates the data pointsfrom their old positions to their new positions. SeriesZoom shrinks the old data tonothing and grows the new data from nothing. SeriesSlide slides out the old data andslides in the new data. These events are typically assigned to the showDataEffect andhideDataEffect of a series object. SeriesInterpolate can be assigned to only the showDataEffect and has no affect when assigned to the hideDataEffect.

This example shows a slide effect that can be used when switching a chart between twodata sets. To see SeriesZoom, comment out the other effects and comment in theSeriesZoom effect. To see SeriesInterpolate, comment out the other effects, commentin the SeriesInterpolate effect, and remove the hideDataEffect property from theColumnSeries tag.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" backgroundColor="0xFFFFFF"> <mx:Script> <![CDATA[ [Bindable] public var chartDP1:Array = [ {day:'Monday',rainfall:10,elevation:100,temperature:78}, {day:'Tuesday',rainfall:7,elevation:220,temperature:66}, {day:'Wednesday',rainfall:5,elevation:540,temperature:55}, {day:'Thursday',rainfall:8,elevation:60,temperature:84}, {day:'Friday',rainfall:11,elevation:390,temperature:52}, {day:'Saturday',rainfall:12,elevation:790,temperature:45}, {day:'Sunday',rainfall:14,elevation:1220,temperature:24} ];

[Bindable] public var chartDP2:Array = [ {day:'Sunday',rainfall:10,elevation:100,temperature:78}, {day:'Saturday',rainfall:7,elevation:220,temperature:66}, {day:'Friday',rainfall:5,elevation:540,temperature:55}, {day:'Thursday',rainfall:8,elevation:60,temperature:84}, {day:'Wednesday',rainfall:11,elevation:390,temperature:52}, {day:'Tuesday',rainfall:12,elevation:790,temperature:45}, {day:'Monday',rainfall:14,elevation:1220,temperature:24} ];

16.2 Add Effects to Charts | 461

Page 6: Flex 3 Cookbook Sample Chapter

]]> </mx:Script>

<mx:SeriesSlide id="dataIn" duration="500" direction="up"/> <mx:SeriesSlide id="dataOut" duration="500" direction="up"/>

<mx:SeriesZoom id="dataOut" duration="500"/>

<mx:SeriesInterpolate id="dataIn" duration="1000"/> --> <mx:BarChart id="rainfallChart" dataProvider="{chartDP1}" > <mx:horizontalAxis> <mx:CategoryAxis dataProvider="{chartDP1}" categoryField="day" /> </mx:horizontalAxis> <mx:series> <mx:ColumnSeries yField="rainfall" xField="day" displayName="rainfall" showDataEffect="{dataIn}" hideDataEffect="{dataOut}" /> </mx:series> </mx:BarChart>

<mx:HBox> <mx:RadioButton groupName="dataProvider" label="Data Provider 1" selected="true" click="rainfallChart.dataProvider=chartDP1;"/> <mx:RadioButton groupName="dataProvider" label="Data Provider 2" click="rainfallChart.dataProvider=chartDP2;"/> </mx:HBox></mx:Application>

16.3 Select Regions of a ChartProblemYou need to select regions or items on your chart.

SolutionUse the selectionMode attribute of your chart to set the selection type you want; thenuse mouse, keyboard, or programmatic means to select items on your chart.

DiscussionJust as with the different list components, chart elements are selectable. This might beused for showing more detail on a data point with data grids or a secondary chart. Tomake a chart selectable, you set the selectionMode attribute to single or multiple (thedefault value of this attribute is none). A none value does not allow any selection, asingle value allows only one item to be selected at a time, and a multiple value allowsmultiple items to be selected.

462 | Chapter 16: Charting

Page 7: Flex 3 Cookbook Sample Chapter

Chart selection can happen via the mouse, via the keyboard, by dragging a rectangularregion to select multiple points, or programmatically in ActionScript. When selectingmultiple items, the first item selected is known as the anchor and the last, the caret.Mouse selection is very straightforward. Simply clicking a chart item puts it in a selectedstate. For multiple selections, hold down the Shift key to select all the items betweenyour first and last selection, or hold down the Ctrl key (or on a Mac, the Commandkey) for noncontiguous multiple selections. Using the left and right arrow keys lets youcycle through the items in a chart. Chart items cycle per series. When the selectionMode attribute is set to multiple, drawing a rectangle in a chart selects all chart itemsthat overlap with that rectangle.

Programmatic selection is a little more complex. A charting selection API lets you accesswhich items are selected and manipulate selection. You can use these attributes of aChartSeries object to get and set selection state:

• selectedItem

• selectedItems

• selectedIndex

• selectedIndices

Alternatively, you can use methods defined in the ChartBase class:

• getNextItem()

• getPreviousItem()

• getFirstItem()

• getLastItem()

Using the Change event of the chart enables you to be notified when the selection changesbased on mouse or keyboard interaction (but not programmatic changes).

The following example shows the selected data for all of the chart’s selected items in aDataGrid. There are also Next and Previous buttons for navigating the selection pro-grammatically.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" backgroundColor="0xFFFFFF"> <mx:Script> <![CDATA[ [Bindable] public var chartDP:Array = [ {day:'Monday',rainfall:10,elevation:100,temperature:78}, {day:'Tuesday',rainfall:7,elevation:220,temperature:66}, {day:'Wednesday',rainfall:5,elevation:540,temperature:55}, {day:'Thursday',rainfall:8,elevation:60,temperature:84}, {day:'Friday',rainfall:11,elevation:390,temperature:52}, {day:'Saturday',rainfall:12,elevation:790,temperature:45}, {day:'Sunday',rainfall:14,elevation:1220,temperature:24} ];

private function changeSelectedIndex(offset:int):void

16.3 Select Regions of a Chart | 463

Page 8: Flex 3 Cookbook Sample Chapter

{ barSeries.selectedIndex+=offset; onSelectionChange(); }

private function onSelectionChange():void { // programmatic changes to chart selection don't fire a Change event,so we need to manually reset // the dataProvider of our detail grid when we programatically changethe selection detailGrid.dataProvider = barChart.selectedChartItems; } ]]> </mx:Script> <!-- use the change event to set the dataProvider of our detail grid to our chart's selected items --> <mx:BarChart id="barChart" dataProvider="{chartDP}" selectionMode="multiple" change="onSelectionChange()"> <mx:verticalAxis> <mx:CategoryAxis dataProvider="{chartDP}" categoryField="day" /> </mx:verticalAxis> <mx:series> <mx:BarSeries id="barSeries" selectedIndex="0" yField="day" xField="rainfall" displayName="day" /> </mx:series> </mx:BarChart> <mx:HBox> <mx:Button click="changeSelectedIndex(1)" label="Previous" /> <mx:Button click="changeSelectedIndex(−1)" label="Next" /> </mx:HBox> <mx:DataGrid id="detailGrid" > <mx:columns> <mx:DataGridColumn dataField="xValue" headerText="rainfall" /> <mx:DataGridColumn dataField="yValue" headerText="day" /> </mx:columns> </mx:DataGrid></mx:Application>

16.4 Format Tick Marks for a ChartProblemYou want to use a custom look for the chart tick marks.

SolutionUse styles on an AxisRenderer to set the desired look for your chart.

464 | Chapter 16: Charting

Page 9: Flex 3 Cookbook Sample Chapter

DiscussionUsing styles, Flex gives you quite a bit of control over where and how tick marks aredisplayed. There are two types of tick marks in Flex charts: major and minor. Majortick marks correspond to axis labels, and minor tick marks appear in-between the majortick marks.

Styles that control tick mark appearance are defined in the AxisRenderer for a chartaxis. Major tick marks can be customized with tickPlacement, tickAlignment, andtickLength. Minor tick marks can be customized with minorTickPlacement, minorTickAlignment, and minorTickLength.

The tickMarkPlacement and minorTickPlacement styles define where the tick mark is inrelation to the axis line. Possible values are listed in Table 16-1.

Table 16-1. Tick Mark Values and Their Placement

Value Placement

cross Across the axis

inside Inside the axis

outside Outside the axis

none No tick mark

This example places the major tick marks inside the chart axis and the minor onesoutside. The major tick marks are 5 pixels long, and the minor tick marks are 10.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" backgroundColor="0xFFFFFF"> <mx:Script> <![CDATA[ [Bindable] public var chartDP:Array = [ {day:'Monday',rainfall:10,elevation:100,temperature:78}, {day:'Tuesday',rainfall:7,elevation:220,temperature:66}, {day:'Wednesday',rainfall:5,elevation:540,temperature:55}, {day:'Thursday',rainfall:8,elevation:60,temperature:84}, {day:'Friday',rainfall:11,elevation:390,temperature:52}, {day:'Saturday',rainfall:12,elevation:790,temperature:45}, {day:'Sunday',rainfall:14,elevation:1220,temperature:24} ];

]]> </mx:Script>

<mx:Style> .customTicks { tickPlacement:inside; minorTickPlacement:outside; tickLength:5; minorTickLength:10; } </mx:Style>

16.4 Format Tick Marks for a Chart | 465

Page 10: Flex 3 Cookbook Sample Chapter

<mx:Canvas label="Area"> <mx:AreaChart dataProvider="{chartDP}" > <mx:horizontalAxis> <mx:CategoryAxis dataProvider="{chartDP}" categoryField="day" /> </mx:horizontalAxis> <mx:verticalAxis> <mx:LinearAxis id="vertAxis" /> </mx:verticalAxis> <mx:verticalAxisRenderers> <mx:AxisRenderer axis="{vertAxis}" styleName="customTicks" /> </mx:verticalAxisRenderers> <mx:series> <mx:AreaSeries yField="rainfall" displayName="rainfall" /> </mx:series> </mx:AreaChart> </mx:Canvas></mx:Application>

16.5 Create a Custom Label for a ChartProblemYou want to customize the chart labels.

SolutionUse styles and label functions.

DiscussionCharts use two kinds of labels: axis labels and data labels. Axis labels are used to showthe values at points along an axis. These can be customized with a label function. Datalabels show the data values at the location of data points and chart elements.

Using axis labels, you can gain greater control over how your axis labels are formatted.If you need specific date formatting or currency formatting, for example, you can usea label function. Label functions also work for data labels.

Label functions for numeric axes, category axes, and series are a bit different, as shownnext.

The numeric axis label function has the following signature:

function_name(labelValue:Object, previousLabelValue:Object, axis:IAxis):String

The parameters are as follows:

466 | Chapter 16: Charting

Page 11: Flex 3 Cookbook Sample Chapter

labelValueThe value of the current label.

previousLabelValueThe value of the label preceding this label. If this is the first label, the value ofpreviousLabelValue is null.

axisThe axis object, such as CategoryAxis or NumericAxis.

The category axis label function has the following signature:

function_name(labelValue:Object, previousLabelValue:Object, axis:axis_type, labelItem:Object):String

categoryValueThe value of the category to be represented.

previousCategoryValueThe value of the previous category on the axis.

axisThe CategoryAxis being rendered.

categoryItemThe item from the data provider that is being represented.

This example uses a CurrencyFormatter for formatting the vertical axis labels and thedata labels:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" backgroundColor="0xFFFFFF" initialize="onInit()"> <mx:Script> <![CDATA[ import mx.charts.chartClasses.Series; import mx.charts.ChartItem; import mx.charts.chartClasses.IAxis; import mx.formatters.CurrencyFormatter;

[Bindable] public var chartDP:Array = [ {month:'Jan',costs:10000,sales:100000}, {month:'Feb',costs:7000,sales:220000}, {month:'Mar',costs:5000,sales:540000}, {month:'April',costs:8000,sales:60000}, {month:'May',costs:11000,sales:390000}, {month:'June',costs:12000,sales:790000}, {month:'July',costs:14000,sales:1220000} ];

private var formatter:CurrencyFormatter;

private function onInit():void { formatter = new CurrencyFormatter(); formatter.currencySymbol = '$'; formatter.precision = 0;

16.5 Create a Custom Label for a Chart | 467

Page 12: Flex 3 Cookbook Sample Chapter

formatter.useThousandsSeparator = true; }

private function currencyAxisLabel(value:Object, previousValue:Object, axis:IAxis):String { return formatter.format(value); }

]]> </mx:Script> <mx:LineChart dataProvider="{chartDP}" > <mx:horizontalAxis> <mx:CategoryAxis dataProvider="{chartDP}" categoryField="month" /> </mx:horizontalAxis> <mx:verticalAxis> <mx:LinearAxis labelFunction="{currencyAxisLabel}" /> </mx:verticalAxis> <mx:series> <mx:LineSeries yField="costs" xField="month" displayName="Costs" /> </mx:series> </mx:LineChart></mx:Application>

16.6 Create a Drill-Down Effect for a Columnar ChartProblemYou want to display an effect when drilling down into chart data to display.

SolutionCreate a new array to populate with data from the chosen item and set thedataProvider of the ColumnChart to that array. Use a SeriesZoom to transition from theoverview data set to the specific data set.

DiscussionThe idea of drilling down into a chart is a UI concept that refers to allowing a user toselect a particular data item from a larger set for closer inspection. A chart’s drill-downeffect enables you to select items in the chart and then display more-specific data aboutthat item. This is achieved simply by setting the dataProvider of the ChartSeries. Aneffect can be played when the dataProvider is changed by setting the showDataEffectand hideDataEffect properties. There are three effects defined in the

468 | Chapter 16: Charting

Page 13: Flex 3 Cookbook Sample Chapter

mx.charts.effects package: SeriesInterpolate, SeriesSlide, and SeriesZoom. This ex-ample uses the SeriesZoom effect. The SeriesZoom class zooms in and out of the datadisplayed in the chart by using a focal point set via the horizontalFocus and verticalFocus attributes.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script><![CDATA[ import mx.charts.series.items.ColumnSeriesItem; import mx.graphics.SolidColor; import mx.charts.ChartItem; import mx.graphics.IFill; import mx.collections.ArrayCollection; import mx.charts.HitData; import mx.charts.events.ChartItemEvent;

This deep data set allows the display of all the items together, as well as allowing eachitem to be shown individually:

[Bindable] public var overview:ArrayCollection = new ArrayCollection ([ { date:"01/02/2006", total:3000, food:1300, drinks:1700, other:0, expenses:2700, profit:300}, { date:"01/08/2006", total:3500, food:1800, drinks:1500, other:200, expenses:2900, profit:600}, { date:"01/15/2006", total:2600, food:1000, drinks:1600, other:0, expenses:2700, profit:−100}, { date:"01/22/2006", total:3200, food:1300, drinks:1900, other:0, expenses:2900, profit:200 }, { date:"02/1/2006", total:2200, food:1200, drinks:1000, other:0, expenses:2100, profit:100 }, { date:"02/8/2006", total:2600, food:1300, drinks:1600, other:100, expenses:2700, profit:400 }, { date:"02/16/2006", total:4100, food:2300, drinks:1700, other:100, expenses:2700, profit:200 }, { date:"02/22/2006", total:4300, food:2300, drinks:1700, other:300, expenses:3300, profit:1000 }]);

[Bindable] public var drillDownDataSet:ArrayCollection;

[Bindable] public var mainDataProvider:ArrayCollection = overview;

private function zoomIntoSeries(e:ChartItemEvent):void { if (mainDataProvider == overview) {

drillDownDataSet = new ArrayCollection(createDataForDate(e)); columnSeries.displayName = "Daily Breakdown"; columnSeries.yField = "amount"; columnSeries.xField = "type";

ca1.categoryField = "type";

mainPanel.title = "Profits for " + e.hitData.item.date; mainDataProvider = drillDownDataSet;

16.6 Create a Drill-Down Effect for a Columnar Chart | 469

Page 14: Flex 3 Cookbook Sample Chapter

} else {

mainDataProvider = overview;

columnSeries.displayName = "Profit by date"; columnSeries.yField = "profit"; columnSeries.xField = "date";

ca1.categoryField = "date";

mainPanel.title = "Profit Overview"; } }

private function profitFunction(element:ChartItem, index:Number):IFill { // black for profit var dateColor:SolidColor = new SolidColor(0x000000);

var item:ColumnSeriesItem = ColumnSeriesItem(element); var profit:Number = Number(item.yValue);

if (profit < 0) { // red for not profitable dateColor.color = 0xFF0000; } return dateColor; }

Here the hitData property of the ChartItemEvent is used to generate the data for thespecific column in the ColumnChart that has been clicked. This will be the data set forthe drilled-down view:

private function createDataForDate(e:ChartItemEvent):Array { var result:Array = [];

var food:Object = { type:"food", amount:e.hitData.item.food }; var drinks:Object = { type:"drinks", amount:e.hitData.item.drinks }; var other:Object = { type:"other", amount:e.hitData.item.other }; var expenses:Object = { type:"expenses", amount:e.hitData.item.expenses };

result.push(food); result.push(drinks); result.push(other); result.push(expenses); return result; }

]]></mx:Script>

<mx:SeriesZoom id="slideZoomIn" duration="1000" verticalFocus="bottom"/> <mx:SeriesZoom id="slideZoomOut" duration="1000" verticalFocus="bottom"/>

<mx:Panel id="mainPanel" title="Profitability"> <mx:ColumnChart id="chart" showDataTips="true" itemClick="zoomIntoSeries

470 | Chapter 16: Charting

Page 15: Flex 3 Cookbook Sample Chapter

(event)" dataProvider="{mainDataProvider}"> <mx:series>

The showDataEffect and hideDataEffect properties indicate which effect will be playedwhen the dataProvider for a ChartSeries is changed:

<mx:ColumnSeries id="columnSeries" displayName="Total profit" fillFunction="profitFunction" yField="profit" xField="date" hideDataEffect="slideZoomOut" showDataEffect="slideZoomIn"/> </mx:series> <mx:horizontalAxis> <mx:CategoryAxis id="ca1" categoryField="date"/> </mx:horizontalAxis> </mx:ColumnChart> </mx:Panel></mx:Application>

16.7 Skin Chart ItemsProblemYou need to skin (change the appearance of) the chart items displayed in a chart.

SolutionCreate a skin class that extends the ProgrammaticSkin class and implements theIDataRenderer interface. Set that skin class to be the itemRenderer for the ChartSeriesof the chart.

DiscussionThe mx.charts.ChartItem is the representation of a data point in the series, so there isone ChartItem for each item in the series’ dataProvider. The ChartItem defines the fol-lowing properties:

currentState : StringDefines the appearance of the ChartItem.

element : IChartElementThe series or element that owns the ChartItem.

index : intThe index of the data from the series’ dataProvider that the ChartItem represents.

item : ObjectThe item from the series’ dataProvider that the ChartItem represents.

itemRenderer : IFlexDisplayObjectThe instance of the chart’s item renderer that represents this ChartItem.

16.7 Skin Chart Items | 471

Page 16: Flex 3 Cookbook Sample Chapter

The ChartItem is owned by a ChartSeries, which uses the items to display the datapoints. A ChartSeries is owned by a Chart, which may have one or more ChartSeriesitems with distinct data properties. Each series has a default renderer that Flex uses todraw that series’ ChartItem objects, as listed in Table 16-2. You can specify a newrenderer to use with the series’ itemRenderer style property. This property points toa class that defines the appearance of the ChartItem object.

Table 16-2. Available itemRenderers for Charts

Chart Type Renderer Classes

AreaChart AreaRenderer

BarChart BoxItemRenderer

BubbleChart CircleItemRenderer

ColumnChart CrossItemRenderer

PlotChart DiamondItemRenderer

ShadowBoxRenderer

TriangleItemRenderer

CandlestickChart CandlesitckItemRenderer

HLOCChart HLOCItemRenderer

LineChart LineRenderer

ShadowLineRenderer

PieChart WedgeItemRenderer

To set an image as the itemRenderer for a series in a chart, simply set an embeddedimage as the itemRenderer property:

<mx:PlotSeries xField="goals" yField="games" displayName="Goals per game" itemRenderer="@Embed(source='../assets/soccerball.png")" radius="20" legendMarkerRenderer="@Embed(source='../assets/soccerball.png')"/>

To create a skin for a chart item, create a ProgrammaticSkin and override theupdateDisplayList method:

package oreilly.cookbook{ import flash.display.BitmapData; import flash.display.DisplayObject; import flash.display.IBitmapDrawable;

import mx.charts.series.items.ColumnSeriesItem; import mx.core.IDataRenderer; import mx.skins.ProgrammaticSkin;

public class CustomRenderer extends ProgrammaticSkin implements IDataRenderer {

private var _chartItem:ColumnSeriesItem;

472 | Chapter 16: Charting

Page 17: Flex 3 Cookbook Sample Chapter

[Embed(source="../../assets/Shakey.png")] private var img:Class;

public function get data():Object { return _chartItem; }

public function set data(value:Object):void { _chartItem = value as ColumnSeriesItem; invalidateDisplayList(); }

override protected function updateDisplayList(unscaledWidth:Number,unscaledHeight:Number):void { super.updateDisplayList(unscaledWidth, unscaledHeight); var img_inst = new img(); var bmd:BitmapData = new BitmapData((img_inst as DisplayObject).height, (foo_inst as DisplayObject).width, true); bmd.draw(img_inst as IBitmapDrawable); graphics.clear(); graphics.beginBitmapFill(bmd); graphics.drawRect(0, 0, unscaledWidth, unscaledHeight); graphics.endFill(); } }}

Now the ProgrammaticSkin class can be set as the itemRenderer of a ColumnSeries:

<mx:ColumnChart id="column" dataProvider="{expenses}"> <mx:horizontalAxis> <mx:CategoryAxis dataProvider="{expenses}" categoryField="Month" /> </mx:horizontalAxis> <mx:series> <mx:Array> <mx:ColumnSeries xField="Month" yField="Expenses" displayName="Expenses" itemRenderer="oreilly.cookbook.CustomRenderer" /> </mx:Array> </mx:series> </mx:ColumnChart>

16.7 Skin Chart Items | 473

Page 18: Flex 3 Cookbook Sample Chapter

16.8 Use ActionScript to Dynamically Add and Remove Columnsfor a ChartProblemYou need to add or remove columns of data in a column chart at runtime.

SolutionUsing series sets in ActionScript, you can dynamically add and remove data series anytime.

DiscussionSeries can be grouped on a chart in a Set object. There are different Set types for eachchart type, as listed in Table 16-3. For example, a ColumnChart uses a ColumnSet to groupColumnSeries. Sets use a type attribute for determining how the series items are grouped.

Table 16-3. Types of Groupings for Series Items

Attribute Description

Clustered Series items are grouped side by side in each category. This is the default for BarCharts and ColumnCharts.

Stacked Series items are stacked on top of each other. Each item represents the cumulative value of the items beneath it.

Overlaid Series items are overlaid on top of each other, with the last series on top.

100 percent Series items are stacked on top of each other, adding up to 100 percent. Each item represents the percentagevalue of the whole set.

Chart sets have a series attribute that accepts an array of series objects. By using sets,you can group your data in many ways. For example, your chart could have two setsof stacked sets, and each stacked set could have a set of clustered series.

By setting up your series and sets in ActionScript, you can add and remove them anytime you like.

The following example uses a data set that tracks millimeters of rainwater throughoutthe day for a week. Each time of day is a series and is initialized in ActionScript. Foreach series, there is a check box; when the check boxes are changed, ColumnSet is re-initialized and adds a series for each selected check box. Finally, the ColumnSet is addedto a new array and set to the series attribute of the column chart.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" backgroundColor="0xFFFFFF" creationComplete="onComplete()"> <mx:Script> <![CDATA[ import mx.charts.series.ColumnSet; import mx.charts.series.ColumnSeries;

474 | Chapter 16: Charting

Page 19: Flex 3 Cookbook Sample Chapter

[Bindable] private var chartDP:Array = [{day:'Monday',dawnRainfall:10,morningRainfall:12,midDayRainfall:6,afternoonRainfall:4,duskRainfall:5},{day:'Tuesday',dawnRainfall:7,morningRainfall:10,midDayRainfall:5,afternoonRainfall:5,duskRainfall:6},{day:'Wednesday',dawnRainfall:5,morningRainfall:9,midDayRainfall:3,afternoonRainfall:2,duskRainfall:3},{day:'Thursday',dawnRainfall:8,morningRainfall:9,midDayRainfall:6,afternoonRainfall:6,duskRainfall:6},{day:'Friday',dawnRainfall:11,morningRainfall:13,midDayRainfall:4,afternoonRainfall:5,duskRainfall:7},{day:'Saturday',dawnRainfall:12,morningRainfall:13,midDayRainfall:9,afternoonRainfall:3,duskRainfall:4},{day:'Sunday',dawnRainfall:14,morningRainfall:12,midDayRainfall:5,afternoonRainfall:1,duskRainfall:3} ];

private var dawnSeries:ColumnSeries; private var morningSeries:ColumnSeries; private var midDaySeries:ColumnSeries; private var afternoonSeries:ColumnSeries; private var duskSeries:ColumnSeries;

private var columnSet:ColumnSet;

private function onComplete():void { //initialize our clustered ColumnSet columnSet = new ColumnSet(); columnSet.type = "clustered";

//initialize all of our series dawnSeries = new ColumnSeries(); dawnSeries.yField = "dawnRainfall"; dawnSeries.xField = "day"; dawnSeries.displayName = "Dawn Rainfall";

morningSeries = new ColumnSeries(); morningSeries.yField = "morningRainfall"; morningSeries.xField = "day"; morningSeries.displayName = "Morning Rainfall";

midDaySeries = new ColumnSeries(); midDaySeries.yField = "midDayRainfall"; midDaySeries.xField = "day"; midDaySeries.displayName = "Mid-day Rainfall";

afternoonSeries = new ColumnSeries(); afternoonSeries.yField = "afternoonRainfall"; afternoonSeries.xField = "day"; afternoonSeries.displayName = "Afternoon Rainfall";

duskSeries = new ColumnSeries(); duskSeries.yField = "duskRainfall"; duskSeries.xField = "day"; duskSeries.displayName = "Dusk Rainfall";

16.8 Use ActionScript to Dynamically Add and Remove Columns for a Chart | 475

Page 20: Flex 3 Cookbook Sample Chapter

updateSeries();

}

Here the series is pushed into the column set, updating the chart:

private function updateSeries():void { //reinit columnSet columnSet.series = new Array(); //for each check box, add the corresponding series if it's checked if(showDawnCheckBox.selected) { columnSet.series.push(dawnSeries); } if(showMorningCheckBox.selected) { columnSet.series.push(morningSeries); } if(showMidDayCheckBox.selected) { columnSet.series.push(midDaySeries); } if(showAfternoonCheckBox.selected) { columnSet.series.push(afternoonSeries); } if(showDuskCheckBox.selected) { columnSet.series.push(duskSeries); } // put columnSet in an array and set to // the chart's "series" attribute rainfallChart.series = [columnSet]; }

]]> </mx:Script> <mx:ColumnChart id="rainfallChart" dataProvider="{chartDP}" > <mx:horizontalAxis> <mx:CategoryAxis categoryField="day" /> </mx:horizontalAxis> <mx:verticalAxis> <mx:LinearAxis minimum="0" maximum="14" /> </mx:verticalAxis> <!-- notice there is no series attribute defined, we do that in AS above --> </mx:ColumnChart> <mx:HBox> <mx:CheckBox id="showDawnCheckBox" label="Dawn Rainfall" selected="true" change="updateSeries()" /> <mx:CheckBox id="showMorningCheckBox" label="Morning Rainfall" change="updateSeries()" /> <mx:CheckBox id="showMidDayCheckBox" label="Mid-day Rainfall" change="updateSeries()" /> <mx:CheckBox id="showAfternoonCheckBox" label="Afternoon Rainfall" change="updateSeries()" /> <mx:CheckBox id="showDuskCheckBox" label="Dusk Rainfall" change="updateSeries()" /> </mx:HBox></mx:Application>

476 | Chapter 16: Charting

Page 21: Flex 3 Cookbook Sample Chapter

16.9 Overlap Multiple ChartSeriesProblemYou want to be able to use different chart types to represent overlapping data sets.

SolutionUse a ColumnChart to hold the multiple charts, and then use an <mx:Series> tag to definethe multiple charts and their properties.

DiscussionAny chart can contain multiple ChartSeries within its series array, each of which canrepresent different fields within a data provider or different data providers. In the fol-lowing example, multiple ChartSeries items are passed to the ColumnChart:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="genData()"> <mx:Script> <![CDATA[

private var DJIA:Number = Math.random()*50 - 20; private var NASDAC:Number = DJIA - Math.random() * 20; private var SP500:Number = Math.random()*40;

public function genData():void { // assigning the data that the chart is bound to // is best done via a local variable that is then // set to the chart data, rather than adding values to the // dataprovider of the chart var newArr:Array = []; for(var i:int = 0; i<10; i++) { DJIA = DJIA + Math.random()*10 - 5; NASDAC = NASDAC - Math.random() * 5; SP500 = Math.random()*40; newArr.push({"DJIA": DJIA, "NASDAC": NASDAC, "SP500": SP500 }); } chartData = newArr; } [Bindable] public var chartData:Array = [];

]]> </mx:Script> <mx:SeriesInterpolate id="eff" elementOffset="1" minimumElementDuration="40" duration="2000"/> <mx:Button click="genData()" label="Generate data"/> <mx:ColumnChart y="100" width="100%" height="100%" dataProvider="{chartData}"> <mx:series> <mx:ColumnSeries showDataEffect="{eff}" yField="DJIA"/> <mx:ColumnSeries showDataEffect="{eff}" yField="NASDAC"/>

16.9 Overlap Multiple ChartSeries | 477

Page 22: Flex 3 Cookbook Sample Chapter

<mx:ColumnSeries showDataEffect="{eff}" yField="SP500"/> </mx:series> </mx:ColumnChart></mx:Application>

The multiple ColumnSeries objects will be rendered one atop the other in theColumnChart.

16.10 Drag and Drop Items in a ChartProblemYou need to drag items from a data source into a chart.

SolutionOverride the dragEnterHandler and dragDropHandler methods of a chart component tocreate a chart with dragging and dropping enabled.

DiscussionThe drag-and-drop feature of a chart works much the same as the drag-and-drop featureof any other component in the Flex Framework: The parent component defines a han-dler for the mouseMove event of the chart and a handler for the dragDrop event of thechart that will receive the dropped data. In the following example, two pie charts havetheir dragEnabled and dropEnabled properties set to true and have two separate ArrayCollections used as their dataProviders. When data is dropped from one component,it is removed from the dragged component and added to the dataProvider of the other.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:cookbook="oreilly.cookbook.*"> <mx:Script> <![CDATA[ import mx.events.DragEvent; import mx.charts.PieChart; import mx.core.IUIComponent; import mx.core.DragSource; import mx.containers.Panel; import mx.managers.DragManager; import mx.collections.ArrayCollection;

[Bindable] private var masterArrColl:ArrayCollection = new ArrayCollection([{ "name":"C Ronaldo", "sog":128, "goals":20, "games":33 }, { "name":"A Adebayor", "sog":128, "goals":20, "games":35 }, { "name":"F Torres", "sog":98, "goals":18, "games":32 }, { "name":"W Rooney", "sog":89, "goals":17, "games":34 }, { "name":"D Drogba", "sog":114, "goals":16, "games":31 }]);

[Bindable] private var subColl:ArrayCollection = new ArrayCollection([{ "name":

478 | Chapter 16: Charting

Page 23: Flex 3 Cookbook Sample Chapter

"C Ronaldo", "sog":128, "goals":20, "games":33 }, { "name":"A Adebayor", "sog":128, "goals":20, "games":35 }]);

// Initializes the drag-and-drop operation. private function mouseMoveHandler(event:MouseEvent):void {

event.preventDefault();

// Get the drag initiator component from the event object. var dragInitiator:PieChart = PieChart(event.currentTarget);

// Create a DragSource object. var ds:DragSource = new DragSource(); //make sure that the chart has a selected item if(dragInitiator.selectedChartItem == null) return; // Call the DragManager doDrag() method to start the drag. DragManager.doDrag(dragInitiator, ds, event); }

The mouseMoveHandler method passes the dragInitiator, the component that has dis-patched the dragging event to the DragManager.doDrag manager, along with theDataSource object (which is not used in this example), and the mouse event that initiatedthe action.

// Called if the target accepts the dragged object and the user // releases the mouse button while over the Canvas container. private function dragDropHandler(event:DragEvent):void {

// Get the selected data from the chart var index:Number = (event.dragInitiator as PieChart).selectedChartItem.index; (event.currentTarget as PieChart).dataProvider.addItem((event.dragInitiator as PieChart).dataProvider.getItemAt(index)); (event.dragInitiator as PieChart).dataProvider.removeItemAt(index); }

The object is first added to the dataProvider of the currentTarget of the dragEvent, thatis, the PieChart where the component is being dropped. The data (and correspondingobject) is then removed from the DragEvent.dragInitiator, and the PieChart that hashad data dragged from it.

]]> </mx:Script> <mx:PieChart dataProvider="{subColl}" selectionMode="single" dragEnabled="true" dropEnabled="true" mouseMove="mouseMoveHandler(event)" dragDrop="dragDropHandler(event)"> <mx:series> <mx:PieSeries field="goals" nameField="name" labelField="name" labelPosition="callout" selectable="true"/> </mx:series> </mx:PieChart> <mx:PieChart dataProvider="{masterArrColl}" dragEnabled="true" dropEnabled="true"selectionMode="single" mouseMove="mouseMoveHandler(event)" dragDrop="dragDropHandler(event)"> <mx:series>

16.10 Drag and Drop Items in a Chart | 479

Page 24: Flex 3 Cookbook Sample Chapter

<mx:PieSeries field="goals" nameField="name" labelField="name" labelPosition="callout" selectable="true"/> </mx:series> </mx:PieChart></mx:Application>

The selectable property of both PieSeries components must be set to true, as must thedragEnabled and dropEnabled properties of both PieCharts. When a ChartItem is drag-ged from the Chart, the dragProxy is rendered as a bitmap copy of the ChartItem beingdragged.

16.11 Create an Editable Line ChartProblemYou need to update the values of one property in the data provider of one chart basedon changes to the other properties.

SolutionCreate a Chart with multiple ChartSeries objects and set each changeableChartSeries to have the selectable property set to true. Then create drag-and-dropevent handlers that will perform any calculations that need to be made when a value ischanged.

DiscussionIn the following example, a series of charts are set up to represent the profit relationshipbetween expenses and sales for a given date. The LineSeries objects that represent theexpenses and sales both have their selectable property set to true:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import mx.charts.chartClasses.AxisLabelSet; import mx.core.DragSource; import mx.charts.series.LineSeries; import mx.events.DragEvent; import mx.managers.DragManager;

import mx.collections.ArrayCollection;

[Bindable] private var expensesAC:ArrayCollection = new ArrayCollection( [ { Month: "Jan", Profit: 2000, Expenses: 1500, Sales: 3550}, { Month: "Feb", Profit: 1000, Expenses: 200, Sales: 1200}, { Month: "Mar", Profit: 1500, Expenses: 500, Sales: 2000}, { Month: "Apr", Profit: 1800, Expenses: 1200, Sales: 3000}, { Month: "May", Profit: 2400, Expenses: 575, Sales: 2975}]);

480 | Chapter 16: Charting

Page 25: Flex 3 Cookbook Sample Chapter

// Initializes the drag-and-drop operation. private function mouseMoveHandler(event:MouseEvent):void {

event.preventDefault();

// Get the drag initiator component from the event object. var dragInitiator:LineSeries = LineSeries(event.currentTarget); // if a selectedItem isn't set, ignore the mouse event if(dragInitiator.selectedItem == null) return;

// Create a DragSource object. var ds:DragSource = new DragSource();

// Call the DragManager doDrag() method to start the drag. DragManager.doDrag(dragInitiator, ds, event); }

The mouseMoveHandler processes mouse movements for each of the selection-enabledLineSeries objects. The dragging calls the DragManager.doDrag method, which has thedragInitiator, in this case a LineSeries object, passed to the doDrag method.

private function setDragDropData(event:DragEvent):void {

var index:Number = (event.dragInitiator as LineChart).selectedChartItem.index; var newYVal:Number = (event.dragInitiator as LineChart).mouseY; var selectedSeries:LineSeries = (event.dragInitiator as LineChart).selectedChartItem.element as LineSeries; var editedObj:Object = (event.dragInitiator as LineChart).dataProvider.getItemAt(index); var als:AxisLabelSet = linechart.verticalAxis.getLabelEstimate(); var maxValue:Number = als.labels[als.labels.length - 1].value;

The getLabelEstimate of the IAxis interface, which both the horizontal and verticalaxes of a chart implement, returns an AxisLabelSet object. The AxisLabelSet objectdefines a label property of type array that contains all the labels that are used in theparticular axis. In this case, the last value is used to determine the maximum value ofthe chart. Because this can change each time the user alters the values, it is importantto read this value each time a value is dropped to ensure that the correct maximum forthe chart is used to calculate the value that the user intended.

if(selectedSeries.yField == "Expenses") { var yPos:Number = ((linechart.height - newYVal) / linechart.height); var newVal:Number = maxValue * yPos; editedObj.Expenses = newVal; } else { var yPos:Number = ((linechart.height - newYVal) / linechart.height); var newVal:Number = maxValue * yPos; editedObj.Sales = newVal; }

16.11 Create an Editable Line Chart | 481

Page 26: Flex 3 Cookbook Sample Chapter

editedObj.Profit = editedObj.Sales - editedObj.Expenses; (event.dragInitiator as LineChart).clearSelection();

It is important to call the clearSelection method for the parent chart to ensure thatthe chart selection does not interfere with the processing of mouse events in the chart.The dataProvider for the LineChart is refreshed, forcing the component to redraw.

// force the chart to redraw - note if we weren't using a simple array collection // the data object in the array could dispatch an event, forcing the binding to update (event.dragInitiator as LineChart).dataProvider = expensesAC;

}

]]> </mx:Script>

<mx:Panel title="LineChart and AreaChart Controls Example" height="100%" width="100%" layout="horizontal">

<mx:LineChart id="linechart" height="100%" width="100%" paddingLeft="5" paddingRight="5" dragDrop="setDragDropData(event)" showDataTips="true" dataProvider="{expensesAC}" selectionMode="single" dragEnabled="true" dropEnabled="true">

<mx:horizontalAxis> <mx:CategoryAxis categoryField="Month"/> </mx:horizontalAxis>

The CircleItemRenderer used for each LineSeries is draggable if the selected propertyis set to true. Because the LineSeries representing the Profit property should be cal-culated by the component when the expenses or sales are altered, its selectableproperty is set to false.

<mx:series> <mx:LineSeries selectable="false" id="profitSeries" yField="Profit" form="curve" displayName="Profit" itemRenderer = "mx.charts.renderers.CircleItemRenderer"/> <mx:LineSeries mouseMove="mouseMoveHandler(event)" yField="Expenses"form="curve" displayName="Expenses" selectable="true" itemRenderer = "mx.charts.renderers.CircleItemRenderer"/> <mx:LineSeries mouseMove="mouseMoveHandler(event)" yField="Sales" form="curve" displayName="Sales" selectable="true" itemRenderer = "mx.charts.renderers.CircleItemRenderer"/> </mx:series> </mx:LineChart>

<mx:Legend dataProvider="{linechart}"/>

</mx:Panel></mx:Application>

482 | Chapter 16: Charting