Skip to content

Commit 57f556d

Browse files
committed
Merge pull request #3621 from AgDude/agdude-grouping
Enh(grouping): BREAKING change aggregation options and improve custom…
2 parents bd4eacc + 7b70f7c commit 57f556d

File tree

9 files changed

+467
-256
lines changed

9 files changed

+467
-256
lines changed

misc/tutorial/209_grouping.ngdoc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,7 @@ could potentially interact with people's custom templates.
8181

8282
Tuning the way aggregations work can be done through defining a columnsProcessor that runs with higher (later)
8383
priority than the groupingColumnProcessor (so higher than 400), and that looks for grouped or aggregated columns
84-
and changes things like the treeAggregation.type, or the customTreeAggregationFinalizerFn. A 'complex' tutorial
85-
may be forthcoming that provides examples of this.
84+
and changes things like the treeAggregationFn, or the customTreeAggregationFinalizerFn. See tutorial 320 for an example.
8685

8786

8887
@example
@@ -105,10 +104,10 @@ function will stop working), and writes them to the console.
105104
columnDefs: [
106105
{ name: 'name', width: '30%' },
107106
{ name: 'gender', grouping: { groupPriority: 1 }, sort: { priority: 1, direction: 'asc' }, width: '20%', cellFilter: 'mapGender' },
108-
{ name: 'age', treeAggregation: { type: uiGridGroupingConstants.aggregation.MAX }, width: '20%' },
107+
{ name: 'age', treeAggregationType: uiGridGroupingConstants.aggregation.MAX, width: '20%' },
109108
{ name: 'company', width: '25%' },
110109
{ name: 'state', grouping: { groupPriority: 0 }, sort: { priority: 0, direction: 'desc' }, width: '35%', cellTemplate: '<div><div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents" title="TOOLTIP">{{COL_FIELD CUSTOM_FILTERS}}</div></div>' },
111-
{ name: 'balance', width: '25%', cellFilter: 'currency', treeAggregation: { type: uiGridGroupingConstants.aggregation.AVG }, customTreeAggregationFinalizerFn: function( aggregation ) {
110+
{ name: 'balance', width: '25%', cellFilter: 'currency', treeAggregationType: uiGridGroupingConstants.aggregation.AVG, customTreeAggregationFinalizerFn: function( aggregation ) {
112111
aggregation.rendered = aggregation.value;
113112
} }
114113
],

misc/tutorial/319_complex_trees.ngdoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ you need to be careful how you use it.
3939

4040
Aggregations are set on the columnDef or column in the format:
4141
```
42-
colDef.treeAggregation: { type: uiGridTreeViewConstants.SUM }
42+
colDef.treeAggregationType = uiGridTreeViewConstants.SUM;
4343
```
4444

4545
You can provide a custom aggregation function, but note that the aggregation is performed as a running total, so
46-
your function needs to work within that framework. Refer the documentation for `customTreeAggregationFn` under
46+
your function needs to work within that framework. Refer the documentation for `treeAggregationFn` under
4747
{@link api/ui.grid.treeBase.api:ColumnDef columnDef}.
4848

4949
You can provide a custom finalizer function, which can be used to format or otherwise complete your aggregation
@@ -68,7 +68,7 @@ that shows both the value for that row as well as the aggregated value.
6868
{ name: 'age', width: '20%' },
6969
{ name: 'company', width: '25%' },
7070
{ name: 'state', width: '35%', field: 'address.state' },
71-
{ name: 'balance', width: '25%', treeAggregation: { type: uiGridTreeViewConstants.aggregation.SUM }, cellFilter: 'currency', cellTemplate: '<div class="ui-grid-cell-contents" title="TOOLTIP"><span>{{row.entity.balance CUSTOM_FILTERS}}</span><span ng-if="row.entity[\'$$\' + col.uid]"> ({{row.entity["$$" + col.uid].value CUSTOM_FILTERS}})</span></div>' }
71+
{ name: 'balance', width: '25%', treeAggregationType: uiGridTreeViewConstants.aggregation.SUM, cellFilter: 'currency', cellTemplate: '<div class="ui-grid-cell-contents" title="TOOLTIP"><span>{{row.entity.balance CUSTOM_FILTERS}}</span><span ng-if="row.entity[\'$$\' + col.uid]"> ({{row.entity["$$" + col.uid].value CUSTOM_FILTERS}})</span></div>' }
7272
],
7373
onRegisterApi: function( gridApi ) {
7474
$scope.gridApi = gridApi;

misc/tutorial/320_complex_grouping.ngdoc

Lines changed: 155 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ children of the selected rowHeader.
1313
@example
1414
<example module="app">
1515
<file name="app.js">
16-
var app = angular.module('app', ['ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.grouping', 'ui.grid.edit', 'ui.grid.selection' ]);
16+
var app = angular.module('app', ['ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.grouping', 'ui.grid.edit', 'ui.grid.selection']);
1717

18-
app.controller('MainCtrl', ['$scope', '$http', '$interval', 'uiGridGroupingConstants', '$filter', function ($scope, $http, $interval, uiGridGroupingConstants, $filter ) {
18+
app.controller('MainCtrl', ['$scope', '$http', '$interval', 'uiGridGroupingConstants', '$filter', 'stats', function ($scope, $http, $interval, uiGridGroupingConstants, $filter, stats ) {
1919
var setGroupValues = function( columns, rows ) {
2020
columns.forEach( function( column ) {
2121
if ( column.grouping && column.grouping.groupPriority > -1 ){
22-
column.treeAggregation.type = uiGridGroupingConstants.aggregation.CUSTOM;
23-
column.customTreeAggregationFn = function( aggregation, fieldValue, numValue, row ) {
22+
// Put the balance next to all group labels.
23+
column.treeAggregationFn = function( aggregation, fieldValue, numValue, row ) {
2424
if ( typeof(aggregation.value) === 'undefined') {
2525
aggregation.value = 0;
2626
}
@@ -33,8 +33,6 @@ children of the selected rowHeader.
3333
aggregation.rendered = null;
3434
}
3535
};
36-
} else {
37-
delete column.customTreeAggregationFn;
3836
}
3937
});
4038
return columns;
@@ -44,20 +42,42 @@ children of the selected rowHeader.
4442
enableFiltering: true,
4543
enableGroupHeaderSelection: true,
4644
treeRowHeaderAlwaysVisible: false,
45+
treeCustomAggregations: {
46+
variance: {label: 'var: ', menuTitle: 'Agg: Var', aggregationFn: stats.aggregator.sumSquareErr, finalizerFn: stats.finalizer.variance },
47+
stdev: {label: 'stDev: ', aggregationFn: stats.aggregator.sumSquareErr, finalizerFn: stats.finalizer.stDev},
48+
mode: {label: 'mode: ', aggregationFn: stats.aggregator.mode, finalizerFn: stats.finalizer.mode },
49+
median: {label: 'median: ', aggregationFn: stats.aggregator.accumulate.numValue, finalizerFn: stats.finalizer.median }
50+
},
4751
columnDefs: [
48-
{ name: 'name', width: '30%' },
49-
{ name: 'gender', grouping: { groupPriority: 1 }, sort: { priority: 1, direction: 'asc' }, editableCellTemplate: 'ui-grid/dropdownEditor', width: '20%',
52+
{ name: 'name', width: '15%' },
53+
{ name: 'gender', grouping: { groupPriority: 1 }, sort: { priority: 1, direction: 'asc' }, editableCellTemplate: 'ui-grid/dropdownEditor', width: '10%',
5054
cellFilter: 'mapGender', editDropdownValueLabel: 'gender', editDropdownOptionsArray: [
5155
{ id: 1, gender: 'male' },
5256
{ id: 2, gender: 'female' }
5357
]
5458
},
55-
{ name: 'age', treeAggregation: { type: uiGridGroupingConstants.aggregation.MAX }, width: '20%' },
56-
{ name: 'company', width: '25%' },
57-
{ name: 'state', grouping: { groupPriority: 0 }, sort: { priority: 0, direction: 'desc' }, width: '35%', cellTemplate: '<div><div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents" title="TOOLTIP">{{COL_FIELD CUSTOM_FILTERS}}</div></div>' },
58-
{ name: 'balance', width: '25%', cellFilter: 'currency', treeAggregation: { type: uiGridGroupingConstants.aggregation.AVG }, customTreeAggregationFinalizerFn: function( aggregation ) {
59-
aggregation.rendered = aggregation.value;
60-
} }
59+
{ field: 'age', treeAggregationType: uiGridGroupingConstants.aggregation.MAX, width: '10%' },
60+
{ field: 'age', displayName: 'Age (common)', customTreeAggregationFn: stats.aggregator.mode, width: '10%' },
61+
{ name: 'company', width: '15%' },
62+
{ name: 'state', grouping: { groupPriority: 0 }, sort: { priority: 0, direction: 'desc' }, width: '25%', cellTemplate: '<div><div ng-if="!col.grouping || col.grouping.groupPriority === undefined || col.grouping.groupPriority === null || ( row.groupHeader && col.grouping.groupPriority === row.treeLevel )" class="ui-grid-cell-contents" title="TOOLTIP">{{COL_FIELD CUSTOM_FILTERS}}</div></div>' },
63+
{ field: 'balance', displayName: 'balance (avg)', width: '15%', cellFilter: 'currency',
64+
treeAggregationType: uiGridGroupingConstants.aggregation.AVG,
65+
customTreeAggregationFinalizerFn: function( aggregation ) {
66+
aggregation.rendered = aggregation.value;
67+
}
68+
},
69+
{ field: 'balance', displayName: 'balance (total)', width: '15%', cellFilter: 'currency',
70+
treeAggregationType: uiGridGroupingConstants.aggregation.SUM,
71+
customTreeAggregationFinalizerFn: function( aggregation ) {
72+
aggregation.rendered = aggregation.value;
73+
}
74+
},
75+
{ field: 'balance', displayName: 'balance (median)', width: '15%', cellFilter: 'currency',
76+
treeAggregationType: 'median',
77+
},
78+
{ field: 'balance', displayName: 'balance (std dev)', width: '15%', cellFilter: 'currency',
79+
treeAggregationType: 'stdev',
80+
}
6181
],
6282
onRegisterApi: function( gridApi ) {
6383
$scope.gridApi = gridApi;
@@ -109,12 +129,132 @@ children of the selected rowHeader.
109129
return input;
110130
}
111131
};
132+
})
133+
.service('stats', function(){
134+
135+
var coreAccumulate = function(aggregation, value){
136+
initAggregation(aggregation);
137+
if ( angular.isUndefined(aggregation.stats.accumulator) ){
138+
aggregation.stats.accumulator = [];
139+
}
140+
if ( !isNaN(value) ){
141+
aggregation.stats.accumulator.push(value);
142+
}
143+
};
144+
145+
var initAggregation = function(aggregation){
146+
/* To be used in conjunction with the cleanup finalizer */
147+
if (angular.isUndefined(aggregation.stats) ){
148+
aggregation.stats = {sum: 0};
149+
}
150+
};
151+
152+
var increment = function(obj, prop){
153+
/* if the property on obj is undefined, sets to 1, otherwise increments by one */
154+
if ( angular.isUndefined(obj[prop])){
155+
obj[prop] = 1;
156+
}
157+
else {
158+
obj[prop]++;
159+
}
160+
};
161+
162+
var service = {
163+
aggregator: {
164+
accumulate: {
165+
/* This is to be used with the uiGrid customTreeAggregationFn definition,
166+
* to accumulate all of the data into an array for sorting or other operations by customTreeAggregationFinalizerFn
167+
* In general this strategy is not the most efficient way to generate grouped statistics, but
168+
* sometime is the only way.
169+
*/
170+
numValue: function (aggregation, fieldValue, numValue) {
171+
return coreAccumulate(aggregation, numValue);
172+
},
173+
fieldValue: function (aggregation, fieldValue) {
174+
return coreAccumulate(aggregation, fieldValue);
175+
}
176+
},
177+
mode: function(aggregation, fieldValue){
178+
initAggregation(aggregation);
179+
var thisValue = fieldValue;
180+
if (angular.isUndefined(thisValue) || thisValue === null){
181+
thisValue = aggregation.col.grid.options.groupingNullLabel;
182+
}
183+
increment(aggregation.stats, thisValue);
184+
if ( aggregation.stats[thisValue] > aggregation.maxCount || angular.isUndefined(aggregation.maxCount) ){
185+
aggregation.maxCount = aggregation.stats[thisValue];
186+
aggregation.value = thisValue;
187+
}
188+
},
189+
sumSquareErr: function(aggregation, fieldValue, numValue){
190+
initAggregation(aggregation);
191+
if ( !isNaN(numValue) ){
192+
increment(aggregation.stats, 'count');
193+
}
194+
aggregation.stats.sum += numValue || 0;
195+
service.aggregator.accumulate.numValue(aggregation, fieldValue, numValue);
196+
}
197+
},
198+
finalizer: {
199+
cleanup: function (aggregation) {
200+
delete aggregation.stats;
201+
if ( angular.isUndefined(aggregation.rendered) ){
202+
aggregation.rendered = aggregation.value;
203+
}
204+
},
205+
median: function(aggregation){
206+
aggregation.stats.accumulator.sort();
207+
var arrLength = aggregation.stats.accumulator.length;
208+
aggregation.value = arrLength % 2 === 0 ?
209+
(aggregation.stats.accumulator[(arrLength / 2) - 1] + aggregation.stats.accumulator[(arrLength / 2)]) / 2
210+
: aggregation.stats.accumulator[(arrLength / 2) | 0];
211+
service.finalizer.cleanup(aggregation);
212+
},
213+
sumSquareErr: function(aggregation){
214+
aggregation.value = 0;
215+
if ( aggregation.count !== 0 ){
216+
var mean = aggregation.stats.sum/aggregation.stats.count,
217+
error;
218+
219+
angular.forEach(aggregation.stats.accumulator, function(value){
220+
error = value - mean;
221+
aggregation.value += error * error;
222+
});
223+
}
224+
},
225+
variance: function(aggregation){
226+
service.finalizer.sumSquareErr(aggregation);
227+
aggregation.value = aggregation.value / aggregation.stats.count;
228+
service.finalizer.cleanup(aggregation);
229+
aggregation.rendered = Math.round(aggregation.value * 100)/100;
230+
},
231+
varianceP: function(aggregation){
232+
service.finalizer.sumSquareErr(aggregation);
233+
if ( aggregation.count !== 0 ) {
234+
aggregation.value = aggregation.value / (aggregation.stats.count - 1);
235+
}
236+
service.finalizer.cleanup(aggregation);
237+
},
238+
stDev: function(aggregation){
239+
service.finalizer.variance(aggregation);
240+
aggregation.value = Math.sqrt(aggregation.value);
241+
aggregation.rendered = Math.round(aggregation.value * 100)/100;
242+
},
243+
stDevP: function(aggregation){
244+
service.finalizer.varianceP(aggregation);
245+
aggregation.value = Math.sqrt(aggregation.value);
246+
aggregation.rendered = Math.round(aggregation.value * 100)/100;
247+
}
248+
},
249+
};
250+
251+
return service;
112252
});
113253
</file>
114254

115255
<file name="index.html">
116256
<div ng-controller="MainCtrl">
117-
<div id="grid1" ui-grid="gridOptions" ui-grid-grouping ui-grid-edit ui-grid-selection class="grid"></div>
257+
<div id="grid1" ui-grid="gridOptions" ui-grid-grouping ui-grid-edit ui-grid-selection class="grid" style="width:100%;"></div>
118258
</div>
119259
</file>
120260

misc/tutorial/401_AllFeatures.ngdoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ All features are enabled to get an idea of performance
1313
<file name="app.js">
1414
var app = angular.module('app', ['ngTouch', 'ui.grid', 'ui.grid.cellNav', 'ui.grid.edit', 'ui.grid.resizeColumns', 'ui.grid.pinning', 'ui.grid.selection', 'ui.grid.moveColumns', 'ui.grid.exporter', 'ui.grid.importer', 'ui.grid.grouping']);
1515

16-
app.controller('MainCtrl', ['$scope', '$http', '$timeout', '$interval', 'uiGridConstants',
17-
function ($scope, $http, $timeout, $interval, uiGridConstants) {
16+
app.controller('MainCtrl', ['$scope', '$http', '$timeout', '$interval', 'uiGridConstants', 'uiGridGroupingConstants',
17+
function ($scope, $http, $timeout, $interval, uiGridConstants, uiGridGroupingConstants) {
1818

1919
$scope.gridOptions = {};
2020
$scope.gridOptions.data = 'myData';
@@ -35,7 +35,7 @@ All features are enabled to get an idea of performance
3535
$scope.gridOptions.columnDefs = [
3636
{ name:'id', width:50 },
3737
{ name:'name', width:100 },
38-
{ name:'age', width:100, enableCellEdit: true, aggregationType:uiGridConstants.aggregationTypes.avg },
38+
{ name:'age', width:100, enableCellEdit: true, aggregationType:uiGridConstants.aggregationTypes.avg, treeAggregationType: uiGridGroupingConstants.aggregation.AVG },
3939
{ name:'address.street', width:150, enableCellEdit: true },
4040
{ name:'address.city', width:150, enableCellEdit: true },
4141
{ name:'address.state', width:50, enableCellEdit: true },

0 commit comments

Comments
 (0)