ag-grid-enterprise
Version:
ag-Grid Enterprise Features
252 lines (200 loc) • 9.24 kB
text/typescript
import {
_,
Bean,
IRowNodeStage,
Autowired,
GridOptionsWrapper,
ColumnController,
ValueService,
RowNode,
Column,
Utils,
StageExecuteParams,
IAggFunc,
ChangedPath
} from "ag-grid-community";
import {PivotStage} from "./pivotStage";
import {AggFuncService} from "../aggregation/aggFuncService";
interface AggregationDetails {
changedPath: ChangedPath;
valueColumns: Column[];
pivotColumns: Column[];
}
export class AggregationStage implements IRowNodeStage {
private gridOptionsWrapper: GridOptionsWrapper;
private columnController: ColumnController;
private valueService: ValueService;
private pivotStage: PivotStage;
private aggFuncService: AggFuncService;
// it's possible to recompute the aggregate without doing the other parts
// + gridApi.recomputeAggregates()
public execute(params: StageExecuteParams): any {
// we don't do aggregation if doing legacy tree good
let doingLegacyTreeData = _.exists(this.gridOptionsWrapper.getNodeChildDetailsFunc());
if (doingLegacyTreeData) { return null; }
let aggDetails = this.createAggDetails(params);
this.recursivelyCreateAggData(params.rowNode, aggDetails);
}
private createAggDetails(params: StageExecuteParams): AggregationDetails {
let pivotActive = this.columnController.isPivotActive();
let measureColumns = this.columnController.getValueColumns();
let pivotColumns = pivotActive ? this.columnController.getPivotColumns() : [];
let aggDetails = <AggregationDetails> {
changedPath: params.changedPath,
valueColumns: measureColumns,
pivotColumns: pivotColumns
};
return aggDetails;
}
private recursivelyCreateAggData(rowNode: RowNode, aggDetails: AggregationDetails) {
// aggregate all children first, as we use the result in this nodes calculations
rowNode.childrenAfterFilter.forEach( (child: RowNode) => {
let nodeHasChildren = child.hasChildren();
if (nodeHasChildren) {
this.recursivelyCreateAggData(child, aggDetails);
} else {
if (child.aggData) {
child.setAggData(null);
}
}
});
//Optionally prevent the aggregation at the root Node
//https://ag-grid.atlassian.net/browse/AG-388
let isRootNode = rowNode.level === -1;
if (isRootNode) {
let notPivoting = !this.columnController.isPivotMode();
let suppressAggAtRootLevel = this.gridOptionsWrapper.isSuppressAggAtRootLevel();
if (suppressAggAtRootLevel && notPivoting) { return; }
}
let skipBecauseNoChangedPath = aggDetails.changedPath.isActive()
&& !aggDetails.changedPath.isInPath(rowNode);
if (skipBecauseNoChangedPath) { return; }
this.aggregateRowNode(rowNode, aggDetails);
}
private aggregateRowNode(rowNode: RowNode, aggDetails: AggregationDetails): void {
let measureColumnsMissing = aggDetails.valueColumns.length === 0;
let pivotColumnsMissing = aggDetails.pivotColumns.length === 0;
let userFunc = this.gridOptionsWrapper.getGroupRowAggNodesFunc();
let aggResult: any;
if (userFunc) {
aggResult = userFunc(rowNode.childrenAfterFilter);
} else if (measureColumnsMissing) {
aggResult = null;
} else if (pivotColumnsMissing) {
aggResult = this.aggregateRowNodeUsingValuesOnly(rowNode, aggDetails);
} else {
aggResult = this.aggregateRowNodeUsingValuesAndPivot(rowNode);
}
rowNode.setAggData(aggResult);
// if we are grouping, then it's possible there is a sibling footer
// to the group, so update the data here also if there is one
if (rowNode.sibling) {
rowNode.sibling.setAggData(aggResult);
}
}
private aggregateRowNodeUsingValuesAndPivot(rowNode: RowNode): any {
let result: any = {};
let pivotColumnDefs = this.pivotStage.getPivotColumnDefs();
// Step 1: process value columns
pivotColumnDefs
.filter(v => !Utils.exists(v.pivotTotalColumnIds)) // only process pivot value columns
.forEach(valueColDef => {
let keys: string[] = valueColDef.pivotKeys;
let values: any[];
let valueColumn: Column = valueColDef.pivotValueColumn;
if (rowNode.leafGroup) {
// lowest level group, get the values from the mapped set
values = this.getValuesFromMappedSet(rowNode.childrenMapped, keys, valueColumn);
} else {
// value columns and pivot columns, non-leaf group
values = this.getValuesPivotNonLeaf(rowNode, valueColDef.colId);
}
result[valueColDef.colId] = this.aggregateValues(values, valueColumn.getAggFunc());
});
// Step 2: process total columns
pivotColumnDefs
.filter(v => Utils.exists(v.pivotTotalColumnIds)) // only process pivot total columns
.forEach(totalColDef => {
let aggResults: any[] = [];
//retrieve results for colIds associated with this pivot total column
totalColDef.pivotTotalColumnIds.forEach((colId: string) => {
aggResults.push(result[colId]);
});
result[totalColDef.colId] = this.aggregateValues(aggResults, totalColDef.pivotValueColumn.getAggFunc());
});
return result;
}
private aggregateRowNodeUsingValuesOnly(rowNode: RowNode, aggDetails: AggregationDetails): any {
let result: any = {};
let changedValueColumns = aggDetails.changedPath.isActive() ?
aggDetails.changedPath.getValueColumnsForNode(rowNode, aggDetails.valueColumns)
: aggDetails.valueColumns;
let notChangedValueColumns = aggDetails.changedPath.isActive() ?
aggDetails.changedPath.getNotValueColumnsForNode(rowNode, aggDetails.valueColumns)
: null;
let values2d = this.getValuesNormal(rowNode, changedValueColumns);
let oldValues = rowNode.aggData;
changedValueColumns.forEach( (valueColumn: Column, index: number) => {
result[valueColumn.getId()] = this.aggregateValues(values2d[index], valueColumn.getAggFunc());
});
if (notChangedValueColumns && oldValues) {
notChangedValueColumns.forEach( (valueColumn: Column) => {
result[valueColumn.getId()] = oldValues[valueColumn.getId()];
});
}
return result;
}
private getValuesPivotNonLeaf(rowNode: RowNode, colId: string): any[] {
let values: any[] = [];
rowNode.childrenAfterFilter.forEach( (node: RowNode) => {
let value = node.aggData[colId];
values.push(value);
});
return values;
}
private getValuesFromMappedSet(mappedSet: any, keys: string[], valueColumn: Column): any[] {
let mapPointer = mappedSet;
keys.forEach( key => mapPointer = mapPointer ? mapPointer[key] : null );
if (!mapPointer) {
return [];
}
let values: any = [];
mapPointer.forEach( (rowNode: RowNode) => {
let value = this.valueService.getValue(valueColumn, rowNode);
values.push(value);
});
return values;
}
private getValuesNormal(rowNode: RowNode, valueColumns: Column[]): any[][] {
// create 2d array, of all values for all valueColumns
let values: any[][] = [];
valueColumns.forEach( ()=> values.push([]) );
let valueColumnCount = valueColumns.length;
let rowCount = rowNode.childrenAfterFilter.length;
for (let i = 0; i<rowCount; i++) {
let childNode = rowNode.childrenAfterFilter[i];
for (let j = 0; j<valueColumnCount; j++) {
let valueColumn = valueColumns[j];
// if the row is a group, then it will only have an agg result value,
// which means valueGetter is never used.
let value = this.valueService.getValue(valueColumn, childNode);
values[j].push(value);
}
}
return values;
}
private aggregateValues(values: any[], aggFuncOrString: string | IAggFunc): any {
let aggFunction: IAggFunc;
if (typeof aggFuncOrString === 'string') {
aggFunction = this.aggFuncService.getAggFunc(<string>aggFuncOrString);
} else {
aggFunction = <IAggFunc> aggFuncOrString;
}
if (typeof aggFunction !== 'function') {
console.error(`ag-Grid: unrecognised aggregation function ${aggFuncOrString}`);
return null;
}
return aggFunction(values);
}
}