Rendezvous with technology

GroupingCollection with improved performance

Posted on: November 4, 2009

There were some concerns over the performance of GroupingCollection class while grouping and calculating summaries.

So, a new class GroupingCollection2 is introduced in the data visualization components.

The major improvements in GroupingCollection2 are -
1. Grouping performance improved.
2. Summary calculation performance improved. Now, instead of looping over the data again and again, summaries are calculated in a single loop.
3. When using async refresh, the summaries are calculated as soon as each Group is built. Earlier, the summaries were calculated only after all the Groups have been made.

What has changed -
1. Introduced class GroupingCollection2 which replaces existing class GroupingCollection.
2. Introduced class SummaryField2 which replaces existing class SummaryField.

No no, classes GroupingCollection and SummaryField are not removed.
They will continue to exist to maintain backward compatibility.
So, you can use either GroupingCollection or GroupingCollection2 and AdvancedDataGrid will continue to work :)

The difference between SummaryField and SummaryField2 is -
SummaryField2 does not have “operation” and “summaryFunction” properties.
A new property “summaryOperation” in added in SummaryField2. It is an Object which takes one of the following -
* SUM, MIN, MAX, AVG or COUNT as String.
OR
* An implemtatation of mx.collections.ISummaryCalculator for calculating custom summaries.
The default value is SUM.

API changes -
1. Method refresh() has been changed in GroupingCollection2. The syntax now is –

function refresh(async:Boolean = false, 
		dispatchCollectionEvents:Boolean = false):Boolean;

Code changes required to start using GroupingCollection2 -
1. Use GroupingCollection2 instead of GroupingCollection.
2. Use SummaryField2 instead of SummaryField.
3. Replace operation/summaryFunction in SummaryField with summaryOperation in SummaryField2.

An example is shown here:
With GroupingCollection –

<mx:GroupingCollection id="gc" source="{arr}">
	<mx:Grouping>
		<mx:GroupingField name="name" >
			<mx:SummaryRow>
				<mx:SummaryField dataField="sal" 
					operation="MAX" />
			</mx:SummaryRow>
		</mx:GroupingField>
	</mx:Grouping>
</mx:GroupingCollection>

With GroupingCollection2 –

<mx:GroupingCollection2 id="gc" source="{arr}">
	<mx:Grouping>
		<mx:GroupingField name="name">
			<mx:SummaryRow >
				<mx:SummaryField2 dataField="sal" 
					summaryOperation="MAX" />
			</mx:SummaryRow>
		</mx:GroupingField>
	</mx:Grouping>
</mx:GroupingCollection2>

Try it and let us know your feedback.

About these ads

43 Responses to "GroupingCollection with improved performance"

Where can we find the release. Couldn’t find anything in Adobe SVN.

Thanks

You can download Flash Builder 4 beta 2 release from http://labs.adobe.com/technologies/flashbuilder4/. This beta release contains the data visualization components with GroupingCollection2.

This remains a Flex 4 only class right? Any chance this iwll get officially backported to Flex 3? I’m using Sreenivas’s GC2 without issues but it would be nice if it was ‘offical’ http://flexpearls.blogspot.com/2008/06/groupingcollection-with-some-better.html

I don’t think these classes will be officially backported to Flex 3.x. Though, they should work if used with Flex 3.x on your own.

Copied and pasted GC2 classes into Flex3.4 with no issues. The only change I had to make was to implement labelFunction for my grouped column as Flex didnt seem to give my expand/collapse nodes a label

Hi Tom,
You can set the Grouping.label property to the dataField of the grouped column. That way, you’ll not need a labelFunction.

Cool, didnt know there was such a property, is this something new in GC2?

So, when the groups are made in GroupingCollection, the group node is stored as {GroupLabel:GROUP_NAME, …}
Now, by default the property is GroupLabel but you can change it by setting the Grouping.label property.
And yes this is present is GroupingCollection also.
The Class Grouping hasn’t changed.

So this GroupLabel I’m aware of, but its interesting how with GC (1) I saw node labels and with GC2 I didn’t. I only had the expand/collapse arrows. And my group is pretty simple as I dont use Summary Rows.

All I used to have is:
groupField1.name = “someDataField”; // this is the label in my provider

Do I need to specify columnName in ADGColumn for this to work?

As a new interface IGroupingCollection2 is introduced, there are some changes in the AdvancedDataGrid class to handle GroupingCollection2. That is why, GroupingCollection2 and Flex 4 AdvancedDataGrid works seamlessly.
But these changes are not present in the 3.4 AdvancedDataGrid and hence you have to do this workaround of setting the Grouping.label property to the same value as the column’s dataField.
You have to specify the same value for column’s dataField and Grouping.label property.

Interesting :) so can I try to copy ADG from Flex 4 into 3.x just the same? Since none of the grid components are true spark are they any more reliable? Seems like next to no work has been done to ADG in 3.x for over a year

HI Tom,
Copying ADG from Flex 4 to Flex 3.x wont work because some things are changed in Flex 4. You can search for and copy the usage of IGroupingCollection2 from Flex 4 ADG to Flex 3.x ADG.
Also, there are many bug fixes and performance improvements which have gone into Flex 3.x and Flex 4 both.

Hi Sameer,
I have a page with two tabs (using view stacks) and advanced datagrid as display component. Both have same grouping fields(say A->-B->C heirarchy). And both have ‘SingleRow’ selection mode. I can move from ADG1 to ADG2 thru context menu.

My issue here is that,if I am right clicking one cell in ADG1 and going to ADG2 I want to move to same cell in the ADG2. This is posssible if I am keeping the selection mode
‘SingleCell’ for ADG2 and finding the row and column index of the selected cell and then setting the same in ADG2.
But I want to keep the selection mode as ‘SingleRow’for getting the selected row from the grid(for showing the graph for selected row) and also for opening the nodes when clicking on any cell(other than grouping field A /B/C). Otherwise this feature will fail.

Could you please help me to solve thse issues?
Also, is there any way to solve the latter issue(if we are going to set ‘singleCell’ and try to solve the first issue)?
Expecting your suggestions..

Hi Aji,
I didn’t quiet get what you are trying to do. Can you elaborate more?
If you use selection mode as single cell, then also you get the selected row. And you mentioned that you want to select the same selected cell in ADG2. Now, with selection mode as SingleRow, you will be able to select the whole row and not cell.

Hi Sameer,
Thanks for your quick response!!!
I have GroupingFields (A,B and C hierarchy) and 12 columns(Jan-Dec) in both ADG.So if I clicking on a cell corresponding to row C and column Jan and moving to ADG2, then same cell should be selected in ADG2(corresponding to C-Jan). As you said we cannot select single
cell if selection mode is’SingleRow’, I have to keep the selection mode as ‘SingleCell’ in ADG2. Now , I am able to point the corresponding cell in ADG2.
But, I think we will not get the grouping field values when clicking on a cell if selection mode is singleCell.

My requirement is that if I am selecting one cell(underany column) in the ADG2, I need to get the correspoding values of the grouping fields A, B and C. and 12 columns. i.e entire selecteItem of the AdvancedGrid. (for showing graphical view of the selected row)
I have tried the following code :(itemClick=”adg_itemClick(event);)

private function adg_itemClick(event:ListEvent):void{

AdvancedDataGrid(event.currentTarget).selectionMode = “singleRow”;
var adgSelectedItem:Object = AdvancedDataGrid(event.currentTarget).
selectedItem;

var selectedA:String = adgSelectedItem["A"];
var selectedB:String = adgSelectedItem["B"];
var selectedA:StringselectedC = adgSelectedItem["C"];

Alert.show(“row A”+selectedA );
Alert.show(“row B”+selectedB);
Alert.show(“row C”+selectedC);

AdvancedDataGrid(event.currentTarget).selectedCells = new Array();
}

I haven’t test it completely. But facing some problems in clearing the initially selected cell (while landing to ADG2 from ADG1).

Thanks

I think if you get the object corresponding to the selected cell, then you’ll get all these values. In case of singleRow mode, you can get the item using adg.selectedItem.
In case of singleCell mode, you can get the rowIndex as –
var rowIndex = adg.selectedCells[0]["rowIndex"];
Now, you can get the item using the cursor –
var cursor:IViewCursor = adg.dataProvider.createCursor();
cursor.seek(CursorBookmark.FIRST, rowIndex);
var selectedItem:Object = cursor.current;

Sameer, you’re a legend.

Hey Sameer,

I’m using Advance datagrid and using combo selection to group columns. For example, If I select “Status” from drop down my grid is group by Status. So my grouping code is something like below which runs on combo selection.

var mygroup:GroupingCollection=new GroupingCollection();
mygroup.source = dominoXML.lastResult.opcenter;

var group:Grouping = new Grouping();
group.fields = [new GroupingField(groupField)];

mygroup.grouping = group;
mygroup.refresh();
Set the dataProvider to the new grouping collection
viewGrid.dataProvider = mygroup

Now I would to display total number of records from each category like below if I assume “Status” is selected in combo .

Status
———————————
Active(2)
bla bla
bla bla
Close(1)
bla bla

Can you please point me to right direction ?

Rishi

hi sameer,
Its working perfectly.. :) thanks a lot!!!

Hi Sameer,

Can we adjust th vertical scroll bar position to center of the ADG when expanding a row ? Currently , if we are expanding the bottom rows, we need scroll down to see the opened nodes. It would be better if opened nodes automaticaly positioned at center /beginning of the grid view.
Please give your thoughts.

Thanks

You can get the opened node by listening to the itemOpen event. Then find the position of the node in the dataProvider by using the IViewCursor code given in my previous post above. Set the verticalScrollPosition or use adg.scrollToIndex() method to scroll to that index.

Hey Sameer,

I’m having problem in sorting my date column in Advance data grid with “DD-MMM-YYYY” format . It doesn’t give proper sort.

I’m using following code in DateTipFunction of Advance data grid ,

private function formatDate(itemA:Object, itemB:Object):int {
//return dateFormatter.format(item.dob);
return ObjectUtil.dateCompare(DateField.stringToDate(itemA.dob, “DD/MMM/YYYY”), DateField.stringToDate(itemB.dob, “DD/MMM/YYYY”));

}

What’s wrong. I must use European format ( DD-MMM-YYYY ) .

Rishi

Are you using the sortCompareFunction?
Are you able to sort the dates without using an AdvancedDataGrid, that is using sorting in your collection?

Sameer,

I’ve being following up your GroupingCollection2 for sometime and finally with the release of FlashBuilder4 gave it a try. I have a very strange effect with wrong calculations in GroupingCollection2. When the same code is applied to the old GroupingCollection the sums are correct? I might be doing something wrong. Please help:

Here is the code:

import mx.collections.ArrayCollection;
import mx.collections.Grouping;
import mx.collections.GroupingCollection;
import mx.collections.GroupingCollection2;
import mx.collections.GroupingField;
import mx.collections.SummaryField;
import mx.collections.SummaryField2;
import mx.collections.SummaryRow;
import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;

[Bindable]
public var myComponentAC:ArrayCollection = new ArrayCollection([
{product:"Procuct 2 ", description:"Description 2 " , abcd:"abcd1", efgh:"Yes", price:"2 " },
{product:"Procuct 3 " ,description:"Description 2 ", abcd:"abcd2" ,efgh:"Yes" ,price:"3 "},
{ product:"Procuct 4 " ,description:"Description 4 " , abcd:"abcd2", efgh:"Yes", price:"4 " },
{product:"Procuct 5 " ,description:"Description 5 " , abcd:"abcd3" ,efgh:"no" ,price:"5 " },
{product:"Procuct 6 " ,description:"Description 7 " , abcd:"abcd3" ,efgh:"no", price:"6 " },
{product:"Procuct 7 " ,description:"Description 7 " , abcd:"abcd4" ,efgh:"no" ,price:"7 " } ]);

public var gp:GroupingCollection = new GroupingCollection();

protected function groupMe():void{
gp.source = myComponentAC;

// add grouping and label
var group:Grouping = new Grouping();
group.label = “Group”;

// add grouping field & summary for a particular field
var groupField1:GroupingField = new GroupingField(“efgh”);
var groupField2:GroupingField = new GroupingField(“abcd”);
var groupField3:GroupingField = new GroupingField(“description”);
var summRow:SummaryRow = new SummaryRow();
var summField:SummaryField = new SummaryField(“product”, “COUNT”);
var summField2:SummaryField = new SummaryField(“price”, “SUM”);

summRow.fields = [summField,summField2];
summRow.summaryPlacement = “group”;
groupField1.summaries = [summRow];
groupField2.summaries = [summRow];
groupField3.summaries = [summRow];

group.fields = [groupField1,groupField2,groupField3];

gp.grouping = group;
gp.refresh();

myADG.dataProvider = gp;

myADG.validateNow();

myADG.expandAll();
}

public var gp2:GroupingCollection2 = new GroupingCollection2();

protected function groupMe2():void{
gp2.source = myComponentAC;

// add grouping and label
var group:Grouping = new Grouping();
group.label = “Group”;

// add grouping field & summary for a particular field
var groupField1:GroupingField = new GroupingField(“efgh”);
var groupField2:GroupingField = new GroupingField(“abcd”);
var groupField3:GroupingField = new GroupingField(“description”);
var summRow:SummaryRow = new SummaryRow();
var summField:SummaryField2 = new SummaryField2(“product”, “COUNT”);
var summField2:SummaryField2 = new SummaryField2(“price”, “SUM”);

summRow.fields = [summField,summField2];
summRow.summaryPlacement = “group”;
groupField1.summaries = [summRow];
groupField2.summaries = [summRow];
groupField3.summaries = [summRow];

group.fields = [groupField1,groupField2,groupField3];

gp2.grouping = group;
gp2.refresh();

myADG.dataProvider = gp2;

myADG.validateNow();

myADG.expandAll();
}

protected function ungroupMe():void{
myADG.dataProvider = myComponentAC;
}

Trying to paste mxml:

Ok modified MXML for the previous post:

VBox width=”100%” height=”100%”

AdvancedDataGrid id= “myADG” dataProvider=”{myComponentAC}” width=”100%” height=”100%”
columns
AdvancedDataGridColumn dataField=”efgh” headerText=”EFGH”
AdvancedDataGridColumn dataField=”product” headerText=”Product COUNT”
AdvancedDataGridColumn dataField=”price” headerText=”Price SUM”

columns
AdvancedDataGrid

Button click=”groupMe()” label=”GroupingCollection”
Button click=”groupMe2()” label=”GroupingCollection2″
Button click=”ungroupMe()” label=”Grouping refresh”
VBox>

I see that you are re-using the same SummaryFields and SummaryRows for all the GroupingFields. This is not recommended. Create different SummaryFields and SummaryRows and assign them to the GroupingFields.

Thanks for the reply, have figured that out a couple of hours ago myself but thanks for pointing out anyway.

Interesting that it worked in the old GroupingCollection with no problems. It would be useful to have that info somewhere on Adobe.. as a lot of people will be moving their dynamic runtime built ADGs from Flex3 to 4…

And yes thank you it does work faster !

…and here is little something that worked for me if anybody is interested:

public var gp2:GroupingCollection2 = new GroupingCollection2();

protected function groupMe2():void{
gp2.source = myComponentAC;

// add grouping and label
var group:Grouping = new Grouping();
group.label = “Group”;

// add grouping field & summary for a particular field
var groupField1:GroupingField = new GroupingField(“efgh”);
groupField1.summaries = [SumRow()];
var groupField2:GroupingField = new GroupingField(“abcd”);
groupField2.summaries = [SumRow()];
var groupField3:GroupingField = new GroupingField(“description”);
groupField3.summaries = [SumRow()];

group.fields = [groupField1,groupField2,groupField3];

gp2.grouping = group;
gp2.refresh();
gp2.refresh();
myADG.dataProvider = gp2;

myADG.validateNow();

myADG.expandAll();
}

private function SumRow():SummaryRow {

var SumFieldArr:Array = new Array();
//for ( … ){
SumFieldArr.push(new SummaryField2(“product”, “COUNT”));
SumFieldArr.push(new SummaryField2(“price”, “SUM”));
//}
var newSumRow:SummaryRow = new SummaryRow();
newSumRow.fields = SumFieldArr;
newSumRow.summaryPlacement = “group”;

return newSumRow;
}

Hi Sameer,

Once items are grouped,How can I allow users to even expand n collapse category by clicking the item, For now they must click on “arrow icon” to expand n collapse.

Thanks,
Rishi

Hi Sameer,

I have a performance problem with ADG. I have a report of around 50K rows and it takes 3-4 minutes to render everytime. All the columns in the grid are String type. I know Numerical column grouping takes little time but I have hav string columns.

I see slight difference when I use GroupingCollection2, SummaryField2, but it is also taking around 3 minutes which is not acceptable.

Could you help me out.

Regards,
GK

Hi,
Please help me ,I have an issue with the count and the filterfunction using adg.I need to filter the repeatitions in Territory,show the correct count.Eg:Territory
a,a,b,b.c.It should show the value as a
b,c and the count should be 5.When I use the filter function I get the count as 3.
public class Extendedgrid extends AdvancedDataGrid
{
override public function set dataProvider(value:Object):void
{ // super.dataProvider=value;

super.dataProvider= groupFn(value);
//Alert.show(“value==>”+groupFn(value));
}
[Bindable]
public var myGroup:GroupingCollection;
[Bindable]
public var grouping1:Grouping ;
[Bindable]
var sumRow:SummaryRow;
[Bindable]
var grpfield:GroupingField;
[Bindable]
var sumField1:SummaryField;
[Bindable]
public var tmpAC:ArrayCollection;

public function groupFn(val:Object):GroupingCollection {

myGroup=new GroupingCollection();
grouping1= new Grouping();
sumRow= new SummaryRow();
sumField1=new SummaryField(“Territory”,”COUNT”)

tmpAC = val as ArrayCollection;
tmpAC.filterFunction = deDupe;
myGroup.source=tmpAC;

grpfield=new GroupingField(“Region”);

sumRow.summaryPlacement=”group”;

//sumField1=new SummaryField(“Region”,”COUNT”)
sumField1.label=”COUNT”;
sumRow.fields=[sumField1];
grpfield.summaries=[sumRow];
grouping1.fields= [grpfield];

myGroup.grouping=grouping1;
myGroup.refresh();
// this.invalidateList();

return myGroup;

}
[Bindable]
public var tempObj:Object = {};
/* [Bindable]
public static var Count:int=0 ;
*/
public function deDupe(item:Object):Boolean {
var retVal:Boolean = false;

if (!tempObj.hasOwnProperty(item.Territory)) {
mx.controls.Alert.show(“groupFn”)
tempObj[item.Territory] = item;
retVal = true ;
//Count=Count+1;

} /* lse if(retVal == true ) {

Count=Count+1;
} */
// mx.controls.Alert.show(“Count”+Count);
return retVal;
}

}
}

You can first group the items and then apply filter function. Something like –
gc.refresh(); // create grouping
adg.dataProvider = gc; // assign to adg
adg.validateNow();
// filter the new collection now
adg.dataProvider.filterFunction = yourFilterFunction;
adg.dataProvider.refresh();

I have done .Not working.Attaching the full code.Please help me.

————————————–
Extendedgrid
—————————————
package
{
import mx.collections.ArrayCollection;
import mx.collections.Grouping;
import mx.collections.GroupingCollection;
import mx.collections.GroupingField;
import mx.collections.SummaryField;
import mx.collections.SummaryRow;
import mx.controls.AdvancedDataGrid;
import mx.controls.Alert;

public class Extendedgrid extends AdvancedDataGrid
{
override public function set dataProvider(value:Object):void
{ super.dataProvider=groupFn(value);

}
[Bindable]
public var myGroup:GroupingCollection;
[Bindable]
public var grouping1:Grouping ;
public var grid:sampleNewGrid;
[Bindable]
var sumRow:SummaryRow;
[Bindable]
var grpfield:GroupingField;
[Bindable]
var sumField1:SummaryField;
[Bindable]
public var tmpAC:ArrayCollection;

public function groupFn(val:Object):GroupingCollection {

myGroup=new GroupingCollection();
grouping1= new Grouping();
sumRow= new SummaryRow();
sumField1=new SummaryField(“Territory”,”COUNT”);
myGroup.source=val as ArrayCollection;

grpfield=new GroupingField(“Region”);

sumRow.summaryPlacement=”group”;

//sumField1=new SummaryField(“Region”,”COUNT”)
sumField1.label=”COUNT”;
sumRow.fields=[sumField1];
grpfield.summaries=[sumRow];
grouping1.fields= [grpfield];

myGroup.grouping=grouping1;
myGroup.refresh();

tmpAC = val as ArrayCollection;
//tmpAC=myGroup as ArrayCollection;
tmpAC.filterFunction = deDupe;

return myGroup;
}

public var tempObj:Object = {};
public function deDupe(item:Object):Boolean {

var retVal:Boolean = false;

if (!tempObj.hasOwnProperty(item.Territory)) {

tempObj[item.Territory] = item;
retVal = true ;

}
return retVal;
}

}
}

—————————
ExtendedGridColumn
——————————-
package
{
import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;
[Style(name="rowColor", type="uint", format="Color", inherit="yes")]
public class ExtendedGridColumn extends AdvancedDataGridColumn
{
public function ExtendedGridColumn(columnName:String=null)
{
super(columnName);
}

}
}

Main mXML also attaching:

nOT ABLE TO ATTACH MANIN MXML,
SAMEER can i HAVE YOUR MAIL ID?

Here is a sample, click the Filter button to get the desired behavior –
<?xml version=”1.0″ encoding=”utf-8″?>
<s:Application xmlns:fx=”http://ns.adobe.com/mxml/2009″
xmlns:s=”library://ns.adobe.com/flex/spark”
xmlns:mx=”library://ns.adobe.com/flex/mx” minWidth=”955″ minHeight=”600″>
<s:layout>
<s:VerticalLayout />
</s:layout>

<fx:Script>
<![CDATA[
import mx.collections.ICollectionView;
protected function button1_clickHandler(event:MouseEvent):void
{
ICollectionView(adg.dataProvider).filterFunction = func;
ICollectionView(adg.dataProvider).refresh();
}

protected function func(o:Object):Boolean
{
if (o.hasOwnProperty("cost") && o["cost"] < 3000)
return false;
return true;
}
]]>
</fx:Script>

<fx:Declarations>
<s:ArrayCollection id="arr">
<fx:Object name="A" sno="1" cost="1000" />
<fx:Object name="A" sno="2" cost="2000" />
<fx:Object name="A" sno="3" cost="3000" />
<fx:Object name="A" sno="4" cost="4000" />
<fx:Object name="A" sno="5" cost="5000" />
</s:ArrayCollection>
</fx:Declarations>
<mx:AdvancedDataGrid id="adg" creationComplete="gc.refresh()">
<mx:dataProvider>
<mx:GroupingCollection2 id="gc" source="{arr}">
<mx:Grouping>
<mx:GroupingField name="name">
<mx:SummaryRow summaryPlacement="group">
<mx:SummaryField2 summaryOperation="COUNT" dataField="name" label="sno" />
</mx:SummaryRow>
</mx:GroupingField>
</mx:Grouping>
</mx:GroupingCollection2>
</mx:dataProvider>
<mx:columns>
<mx:AdvancedDataGridColumn dataField="name" />
<mx:AdvancedDataGridColumn dataField="sno" />
<mx:AdvancedDataGridColumn dataField="cost" />
</mx:columns>
</mx:AdvancedDataGrid>
<s:Button label="Filter" click="button1_clickHandler(event)" />
</s:Application>

The above code does not work in that the “Count” remains “5″ after the collection is filtered.

Hi Sameer,
I used GCollection2 and set the everything as in the examples, but never fixed a problem.
The problem is that myTree shows an unknown RootNode, appears like [object Object], my rootnode seems under this unknown node.
I couldn’t find what I’m missing. What do you think about that

Thanks in advance

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: