Saturday, April 24, 2010

Flex OLAP Aggregators Fix for Null and Undefined Values

There is very little information available on Adobe Flex data visualization components. Sometimes I even question whether it is used in production at all. In one of our recent projects we used OLAP components extensively. It is the first time I had to check out the source of component so often: to check out how it is supposed to work and to fix issues (and there are quite a lot).

Data visualization provides a set of aggregators: sum, average, count, max, min. But there is an issue how they handle null or undefined values. Result defaults to 0(zero) even if there is no value defined for given dataField in a dataprovider or all values are null/undefined. Maybe it is by design, but it is not how it is used in real world scenarios. For example, Flex chart components support null/undefined values and there is even interpolateValues property in LineSeries to handle exactly this situation.

Fortunately, it is easy to fix. Let’s look at mx.olap.aggregators.SumAggregator source code for example. There are 3 methods of interest to us: computeBegin, computeLoop, and computeEnd. First, computeBegin is called and you create a holder object where temporary data will be stored as aggregation proceeds. Next, there is a call to computeLoop for every object in dataprovider where you update your holder object. Finally, computeEnd is called where you compute the final result and return it back. You see, the problem is in computeBegin and computeLoop:

1: public function computeBegin(dataField:String):Object
2: {
3:   var newObj:Object = {};
4:   newObj[dataField] = 0;
5:   return newObj;
6: }
7: 
8: public function computeLoop(data:Object, dataField:String,
9:                             rowData:Object):void
10: {
11:   var value:Number = rowData[dataField];        
12:   if (typeof(value) == "xml")
13:     value = Number(value.toString());
14:   if (!data.hasOwnProperty(dataField))
15:     data[dataField] = value ;
16:   else
17:     data[dataField] += value;
18: }

 

In comuteBegin holder object is immediately initialized to default 0 value (line 4). We can simply comment it, because in computeLoop this case is handled (line 14). But computeLoop has its own problems. There is no check for null/NaN values. So, if some of data objects does not have a value for given dataField the whole result is broken. Another problem is line 12. How Number type variable is supposed to be of type xml is beyond me. This check should be done before casting to Number. Looks like untested and unreviewed code to me. But interesting thing is how it stayed this way for 3 years. Anyway, here is the fixed version for sum aggregator:

public function computeBegin(dataField:String):Object
{
return  {};
}
public function computeLoop(data:Object, dataField:String,
rowData:Object):void
{
  var value:Object = rowData[dataField];
  if(value == null)
    return;
  if (typeof(value) == "xml")
    value = Number(value.toString());
  else
    value = Number(value);
  if(isNaN(value as Number))
    return;
  if (!data.hasOwnProperty(dataField))
    data[dataField] = value;
  else
    data[dataField] += value;
}

Other aggregators can be fixed just the same way.

No comments:

Post a Comment