Drilling down into data

One common use of charts is to allow the user to drill down into the data. This usually occurs when the user performs some sort of event on the chart such as clicking on a wedge in a PieChart control or clicking on a column in a ColumnChart control. Clicking on a data item reveals a new chart that describes the make-up of that data item.

For example, you might have a ColumnChart control that shows the month-by-month production of widgets. To initially populate this chart, you might make a database call (through a service or some other adapter). If the user then clicks on the January column, the application could display the number of widgets of each color that were produced that month. To get the individual month’s widget data, you typically make another database call and pass a parameter to the listening service that describes the specific data you want. You can then use the resulting data provider to render the new view.

Typically, when you drill down into chart data, you create new charts in your application with ActionScript. When creating new charts in ActionScript, you must be sure to create a series, add it to the new chart’s series Array, and then call the addElement() method (on Spark containers) or addChild() method (on MX containers) to add the new chart to the display list. For more information, see Creating charts in ActionScript.

One way to provide drill-down functionality is to make calls that are external to the application to get the drill-down data. You typically do this by using the chart’s itemClick event listener, which gives you access to the HitData object. The HitData object lets you examine what data was underneath the mouse when the event was triggered. This capability lets you perform actions on specific chart data. For more information, see Using the HitData object.

You can also use a simple Event object to get a reference to the series that was clicked. The following example shows the net worth of a fictional person. When you click on a column in the initial view, the example drills down into a second view that shows the change in value of a particular asset class over time.

The following example uses the Event object to get a reference to the clicked ColumnSeries. It then drills down into the single ColumnSeries by replacing the chart’s series Array with the single ColumnSeries in the chart. When you click a column again, the chart returns to its original configuration with all ColumnSeries.

<?xml version="1.0"?>
<!-- charts/SimpleDrillDown.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:mx="library://ns.adobe.com/flex/mx" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    creationComplete="initApp();srv.send();"
    height="600">

    <fx:Declarations>
        <!-- View source of the following page to see the structure of the data that Flex uses in this example. -->
        <mx:HTTPService id="srv" url="http://aspexamples.adobe.com/chart_examples/networth-xml.aspx"/>
        <!-- To see data in an HTML table, go to http://aspexamples.adobe.com/chart_examples/networth.aspx -->  
    </fx:Declarations>

    <fx:Script><![CDATA[
        public var initSeriesArray:Array = new Array();
        public var level:Number = 1;
        public var newSeries:Array;
        
        private function initApp():void {
            // Get initial series Array -- to be reloaded when it returns 
            // from a drill down.
            initSeriesArray = chart.series;             
        }
        
        private function zoomIntoSeries(e:Event):void {
            newSeries = new Array();
            if (level == 1) {
                newSeries.push(e.currentTarget);   
                level = 2; 
            } else {
                newSeries = initSeriesArray;
                p1.title = "Net Worth";
                level = 1;
            }           
            chart.series = newSeries;            
        }
        
    ]]></fx:Script>

    <s:layout>
        <s:VerticalLayout/>
    </s:layout>

    <s:Panel id="p1" title="Net Worth">
         <s:layout>
             <s:VerticalLayout/>
         </s:layout>
        <mx:ColumnChart id="chart" 
            dataProvider="{srv.lastResult.data.result}" 
            type="stacked" 
            showDataTips="true">
            <mx:series> 
                <mx:ColumnSeries id="s1" 
                    displayName="Cash" 
                    yField="cash" 
                    xField="date" 
                    click="zoomIntoSeries(event)"/>
                <mx:ColumnSeries id="s2" 
                    displayName="Stocks" 
                    yField="stocks"  
                    xField="date"  
                    click="zoomIntoSeries(event)"/>
                <mx:ColumnSeries id="s3" 
                    displayName="Retirement" 
                    yField="retirement" 
                    xField="date" 
                    click="zoomIntoSeries(event)"/>
                <mx:ColumnSeries id="s4" 
                    displayName="Home" 
                    yField="home" 
                    xField="date" 
                    click="zoomIntoSeries(event)"/>
                <mx:ColumnSeries id="s5" 
                    displayName="Other" 
                    yField="other" 
                    xField="date" 
                    click="zoomIntoSeries(event)"/>
            </mx:series>            
            <mx:horizontalAxis >
                <mx:DateTimeAxis title="Date" dataUnits="months"/>
            </mx:horizontalAxis>    
        </mx:ColumnChart> 
        <mx:Legend dataProvider="{chart}"/>
    </s:Panel>
</s:Application>

The executing SWF file for the previous example is shown below:

Another approach to drilling down into chart data is to use unused data within the existing data provider. You can do this by changing the properties of the series and axes when the chart is clicked.

The following example is similar to the previous example in that it drills down into the assets of a fictional person’s net worth. In this case, though, it shows the value of the asset classes for the clicked-on month in the drill-down view rather than the change over time of a particular asset class.

This example uses the HitData object’s item property to access the values of the current data provider. By building an Array of objects with the newly-discovered data, the chart is able to drill down into the series without making calls to any external services.

Drilling down into data is an ideal time to use effects such as SeriesSlide. This example also defines seriesIn and seriesOut effects to slide the columns in and out when the drilling down (and the return from drilling down) occurs.

<?xml version="1.0"?>
<!-- charts/DrillDownWithEffects.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:mx="library://ns.adobe.com/flex/mx" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    creationComplete="srv.send();"
    height="600">
     
    <fx:Declarations>
         <!-- View source of the following page to see the structure of the data that Flex uses in this example. -->
         <mx:HTTPService id="srv" url="http://aspexamples.adobe.com/chart_examples/networth-xml.aspx" result="resultHandler()"/>
         <!-- To see data in an HTML table, go to http://aspexamples.adobe.com/chart_examples/networth.aspx -->  

        <mx:SeriesSlide id="slideIn" duration="1000" direction="down"/>
        <mx:SeriesSlide id="slideOut" duration="1000" direction="up"/>

    </fx:Declarations>

    <fx:Script><![CDATA[
        import mx.collections.ArrayCollection;
        import mx.charts.HitData;
        import mx.charts.events.ChartItemEvent;
          
        [Bindable]
        public var drillDownDataSet:ArrayCollection;

        [Bindable]
        public var dp:ArrayCollection;

          // level is a temporary variable used to determine when to drill down.
          private var level:int =  1;

        private function resultHandler():void {
          dp = ArrayCollection(srv.lastResult.data.result);
        }
        
        private function zoomIntoSeries(e:ChartItemEvent):void {
            if (level == 1) {
                drillDownDataSet = new ArrayCollection(genData(e));
                cs1.displayName = "Assets";
                cs1.yField = "amount";
                cs1.xField = "type";

                ca1.categoryField = "type";
                
                p1.title = "Asset breakdown for " + e.hitData.item.date;
                dp = drillDownDataSet;
                
                // Remove the Legend. It is not needed in this view because 
                // each asset class is its own column in the drill down.
                p1.removeElement(myLegend);
                
                level = 2;
            } else {
                cs1.displayName = "All Assets";
                cs1.yField = "assets";
                cs1.xField = "date";
                
                ca1.categoryField = "date";
                
                p1.title = "Net Worth";
                
                // Add the Legend back to the Panel.
                p1.addElement(myLegend);
                
                // Reset chart to original data provider.
                resultHandler();
                
                level = 1;
            }
        }

          // Build an ArrayCollection of objects to make up the new data set.
        private function genData(e:ChartItemEvent):Array {
                var result:Array = [];

                trace("Cash: " + e.hitData.item.cash);
                trace("Stocks: " + e.hitData.item.stocks);
                trace("Retirement: " + e.hitData.item.retirement);
                trace("Home: " + e.hitData.item.home);
                trace("Other: " + e.hitData.item.other);
                
                var o1:Object = {
                    type:"cash",
                    amount:e.hitData.item.cash
                };                      
                var o2:Object = {
                    type:"stocks",
                    amount:e.hitData.item.stocks
                };                      
                var o3:Object = {
                    type:"retirement",
                    amount:e.hitData.item.retirement
                };                      
                var o4:Object = {
                    type:"home",
                    amount:e.hitData.item.home
                };                      
                var o5:Object = {
                    type:"other",
                    amount:e.hitData.item.other
                };                      
                trace(o1.type + ":" + o1.amount);
                
                result.push(o1);
                result.push(o2);
                result.push(o3);
                result.push(o4);
                result.push(o5);
                return result;               
        }
        
    ]]></fx:Script>

    <s:layout>
        <s:VerticalLayout/>
    </s:layout>

    <s:Panel id="p1" title="Net Worth">
        <s:layout>
            <s:VerticalLayout/>
        </s:layout>
        <mx:ColumnChart id="chart" 
            showDataTips="true" 
            itemClick="zoomIntoSeries(event)" 
            dataProvider="{dp}">
            <mx:series> 
                <mx:ColumnSeries id="cs1" 
                    displayName="All Assets" 
                    yField="assets" 
                    xField="date" 
                    hideDataEffect="slideOut" 
                    showDataEffect="slideIn"/>
            </mx:series>            

            <mx:horizontalAxis>
                <mx:CategoryAxis id="ca1" categoryField="date"/>
            </mx:horizontalAxis>
        </mx:ColumnChart> 
        
        <mx:Legend id="myLegend" dataProvider="{chart}"/>
    </s:Panel>
</s:Application>

The executing SWF file for the previous example is shown below:

Another way to drill down into chart data is to use the selection API. You can select a subset of data points on a chart to create a new data provider. For more information, see Selecting chart items.