@@ -13,14 +13,14 @@ children of the selected rowHeader.
13
13
@example
14
14
<example module="app">
15
15
<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']);
17
17
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 ) {
19
19
var setGroupValues = function( columns, rows ) {
20
20
columns.forEach( function( column ) {
21
21
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 ) {
24
24
if ( typeof(aggregation.value) === 'undefined') {
25
25
aggregation.value = 0;
26
26
}
@@ -33,8 +33,6 @@ children of the selected rowHeader.
33
33
aggregation.rendered = null;
34
34
}
35
35
};
36
- } else {
37
- delete column.customTreeAggregationFn;
38
36
}
39
37
});
40
38
return columns;
@@ -44,20 +42,42 @@ children of the selected rowHeader.
44
42
enableFiltering: true,
45
43
enableGroupHeaderSelection: true,
46
44
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
+ },
47
51
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 %',
50
54
cellFilter: 'mapGender', editDropdownValueLabel: 'gender', editDropdownOptionsArray: [
51
55
{ id: 1, gender: 'male' },
52
56
{ id: 2, gender: 'female' }
53
57
]
54
58
},
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
+ }
61
81
],
62
82
onRegisterApi: function( gridApi ) {
63
83
$scope.gridApi = gridApi;
@@ -109,12 +129,132 @@ children of the selected rowHeader.
109
129
return input;
110
130
}
111
131
};
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;
112
252
});
113
253
</file>
114
254
115
255
<file name="index.html">
116
256
<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>
118
258
</div>
119
259
</file>
120
260
0 commit comments