Grouping Flex Chart Data

1. December 2008 23:20

After spending several days bashing my head against the Flex Charting SDK trying to figure out an easy way to group similar items together in a ColumnChart, I finally had to break down tonight and do it the old fashioned way.

I received several ideas on how to approach the problem from the Flex Discussion Board but couldn't get any of them to work.  Mainly, the ideas had to do with trying to use a GroupingCollection and then binding mychart series off of the GroupingCollection.  According to the Flex SDK docs, the groupingcollection works well with the AdvancedDataGrid, and I can confirm that this is correct.  However, applying the groupingCollection to a chart series, does not seem to work.  I decided to use a ColumnSet as a wrapper for my multiple series instead.  this might look like a hack, but I've tested the idea against several data collections and it does what I want.



So here is the issue:

I have a data provider that will provide me with a collection of items in the following format:

{interval:"Y2008", label:"Red", score:1.5},
{interval:"Y2008", label:"Green", score:4.5},
{interval:"Y2008", label:"Purple", score:9.5},
{interval:"Y2007", label:"Red", score:1.5},
{interval:"Y2007", label:"Green", score:4.5},
{interval:"Y2007", label:"Purple", score:3.5},
{interval:"Y2006", label:"Red", score:4.5},
{interval:"Y2006", label:"Green", score:2.5},
{interval:"Y2006", label:"Purple", score:6.5},

For the purposes of this tutorial and for general form, we can assume this is an ArrayCollection.  We are not going to assume that we know how many items are being returned from the data source so our structure needs to be able to handle the data regardless of the actual number returned.  We are going to assume that we know what the xml schema being returned is going to be.

So what we want is a grouping by category with each grouping displaying a subset of our data collection, in this case we want our 3 categories to be grouped by our "interval" property: 



Like I said, using a grouping collection simply doesn't work and I would LOVE for someone to prove me wrong on this cause I tried practically everything to get it to work.

So we have a couple issues.  First, we don't know how many intervals are going to be coming from our data provider.  Second, we don't know how many categories are going to be created.  We want all of our individual data groups to use a yField of "score."  

<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="initApp()"
     horizontalScrollPolicy="off" verticalScrollPolicy="off">

<mx:Script>
        <![CDATA[
            import mx.events.CollectionEvent;
            import mx.collections.ArrayCollection;
            import mx.charts.series.ColumnSet;
            import mx.charts.series.ColumnSeries;
            import mx.collections.Sort;
            import mx.collections.SortField;
            import mx.charts.ColumnChart;
            import mx.charts.Legend;
            import mx.charts.CategoryAxis;
           
            [Bindable]
            public var dataProvider:ArrayCollection = new ArrayCollection();
            [Bindable]
            private var aryCats:ArrayCollection = new ArrayCollection();
            [Bindable]
            private var aryYear:ArrayCollection = new ArrayCollection();
            private var chart:ColumnChart = new ColumnChart();
            private var legend:Legend = new Legend();
           
            private function initApp():void
            {

// Perform a sort to get your collection sorted by your "label and "interval" fields.

                var sort:Sort = new Sort();
                sort.fields = [new SortField("label"), new SortField("interval")];
               
                dataProvider.sort = sort;               
                dataProvider.refresh();


                try
                {
                    for each(var item:Object in dataProvider)
                    {
                        // Determine the number of categories
                        if(aryCats.contains(item.label))
                        {}
                        else
                        {
                            aryCats.addItem(item.label);
                        }
                        // Determine the number of time intervals
                        if(aryYear.contains(item.interval))
                        {}
                        else
                        {
                            aryYear.addItem(item.interval);
                        }                    
                    }   
                }
                catch(e:Error)
                {
                    trace(e.message);
                }
                chart.showDataTips = true;
                chart.dataProvider = dataProvider;
               

// Instead of using a plain ColumnSeries, we are going to group our columnSeries within a columnSet to provide our grouping.
                var colSet:ColumnSet = new ColumnSet();
                colSet.type = "clustered";
               
                // Store the created series
                var serColl:Array = new Array();
                // Create a filter array
                var itemColl:Array;
                try
                {
                    for each(var itm:Object in aryYear)
                    {
                        itemColl = new Array();

// We create a dynamic series for each of the interval items.  Since our series is going to be sub-grouped by our arbitrary field.

                       var series:ColumnSeries = new ColumnSeries();
                        series.yField = "score";
                        series.displayName = itm as String;
                       
                        for each(var obj:Object in dataProvider)
                        {
                            if(obj.interval == itm)
                            {
                                itemColl.push(obj);
                            }
                        }
                        // Just gonna add a little fancy here for the user's benefit.
                        series.setStyle("showDataEffect", slideIn);
                        series.setStyle("hideDataEffect", slideOut);
                        series.setStyle("rollOverEffect", glowIn);
                        series.setStyle("rollOutEffect", glowOut);


                        series.dataProvider = itemColl;
                        serColl.push(series);
                    } 

// Set the new series array as the series provider for the columnset      
                    colSet.series = serColl;
                   
                    var hAxis:CategoryAxis = new CategoryAxis();
                    hAxis.dataProvider = aryCats;
                    hAxis.title = "Colors";
                   
                    legend.y = 0;
                    legend.dataProvider = chart;                   
                   
                    chart.horizontalAxis = hAxis;
                   
                    chart.series.push(colSet);
                    addChild(chart);
                    addChild(legend);   
                }
                catch(e:Error)
                {
                    trace(e.message);
                }
            }
            override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
            {
                super.updateDisplayList(unscaledWidth, unscaledHeight);
                chart.height = this.unscaledHeight;
                chart.width = this.unscaledWidth * .75;
            }
        ]]>
 </mx:Script>

<mx:SeriesSlide id="slideIn" duration="1000" direction="up"/>
<mx:SeriesSlide id="slideOut" duration="1000" direction="down" />
<mx:Glow id="glowIn" inner="true" alphaFrom="0" alphaTo="1" color="#FFFFFF" blurXFrom="0" blurXTo="5" blurYFrom="0" blurYTo="5" duration="300"/>
<mx:Glow id="glowOut" inner="true" alphaFrom="1" alphaTo="0" color="#FFFFFF" blurXFrom="5" blurXTo="0" blurYFrom="5" blurYTo="0" duration="300"/>

</mx:HBox>



This may not be the cleanest implementation, but after looking and trying to get the regular chart creation process to incorporate this functionality, I had to do something.  Most of the examples out there assume that your columnSeries data provider is going to be made up of individual and unique fields from which to bind your columnSeries to.  In this scenario, we are working with identical fields from our data but want to distribute it based uppon the values contained in the fields.

I looked at the performance of this nested loop structure and with a small dataset, I didn't notice any losses.

Hope this helps.

Currently rated 5.0 by 3 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments


Log in