Skip to content

Commit 72bae81

Browse files
authored
Merge pull request #10287 from IgniteUI/mkirova/pivot-dimensions-strategy
Add pivot dimensions strategies
2 parents 0b84199 + 38a214b commit 72bae81

21 files changed

+887
-1140
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
2+
import { IPivotDimension, IPivotKeys, IPivotValue, PivotDimensionType } from '../grids/pivot-grid/pivot-grid.interface';
3+
import { PivotUtil } from '../grids/pivot-grid/pivot-util';
4+
5+
export interface IPivotDimensionStrategy {
6+
process(collection: any,
7+
dimensions: IPivotDimension[],
8+
values: IPivotValue[],
9+
pivotKeys?: IPivotKeys): any[];
10+
}
11+
12+
export class NoopPivotDimensionsStrategy implements IPivotDimensionStrategy {
13+
private static _instance: NoopPivotDimensionsStrategy = null;
14+
15+
public static instance() {
16+
return this._instance || (this._instance = new NoopPivotDimensionsStrategy());
17+
}
18+
19+
public process(collection: any[], _: IPivotDimension[], __: IPivotValue[]): any[] {
20+
return collection;
21+
}
22+
}
23+
24+
25+
export class PivotRowDimensionsStrategy implements IPivotDimensionStrategy {
26+
private static _instance: PivotRowDimensionsStrategy = null;
27+
28+
public static instance() {
29+
return this._instance || (this._instance = new PivotRowDimensionsStrategy());
30+
}
31+
32+
public process(
33+
collection: any,
34+
rows: IPivotDimension[],
35+
values?: IPivotValue[],
36+
pivotKeys: IPivotKeys =
37+
{ aggregations: 'aggregations', records: 'records', children: 'children', level: 'level'}
38+
): any[] {
39+
let hierarchies;
40+
let data;
41+
const prevRowDims = [];
42+
let prevDim;
43+
for (const row of rows) {
44+
if (!data) {
45+
// build hierarchies - groups and subgroups
46+
hierarchies = PivotUtil.getFieldsHierarchy(collection, [row], PivotDimensionType.Row, pivotKeys);
47+
// generate flat data from the hierarchies
48+
data = PivotUtil.processHierarchy(hierarchies, collection[0] ?? [], pivotKeys, 0, true);
49+
row.fieldName = hierarchies.get(hierarchies.keys().next().value).dimension.fieldName;
50+
prevRowDims.push(row);
51+
prevDim = row;
52+
} else {
53+
const newData = [...data];
54+
for (let i = 0; i < newData.length; i++) {
55+
const currData = newData[i][prevDim.fieldName + '_' + pivotKeys.records];
56+
const hierarchyFields = PivotUtil
57+
.getFieldsHierarchy(currData, [row], PivotDimensionType.Row, pivotKeys);
58+
const siblingData = PivotUtil
59+
.processHierarchy(hierarchyFields, newData[i] ?? [], pivotKeys, 0);
60+
row.fieldName = hierarchyFields.get(hierarchyFields.keys().next().value).dimension.fieldName;
61+
PivotUtil.processSiblingProperties(newData[i], siblingData, pivotKeys);
62+
63+
PivotUtil.processSubGroups(row, prevRowDims.slice(0), siblingData, pivotKeys);
64+
if (PivotUtil.getDimensionDepth(prevDim) > PivotUtil.getDimensionDepth(row)) {
65+
newData[i][row.fieldName + '_' + pivotKeys.records] = siblingData;
66+
} else {
67+
newData.splice(i , 1, ...siblingData);
68+
}
69+
i += siblingData.length - 1;
70+
}
71+
data = newData;
72+
prevDim = row;
73+
prevRowDims.push(row);
74+
}
75+
}
76+
return data;
77+
}
78+
}
79+
80+
export class PivotColumnDimensionsStrategy implements IPivotDimensionStrategy {
81+
private static _instance: PivotRowDimensionsStrategy = null;
82+
83+
public static instance() {
84+
return this._instance || (this._instance = new PivotColumnDimensionsStrategy());
85+
}
86+
87+
public process(
88+
collection: any[],
89+
columns: IPivotDimension[],
90+
values: IPivotValue[],
91+
pivotKeys: IPivotKeys = {aggregations: 'aggregations', records: 'records', children: 'children', level: 'level'}
92+
): any[] {
93+
const result = [];
94+
collection.forEach(hierarchy => {
95+
// apply aggregations based on the created groups and generate column fields based on the hierarchies
96+
this.groupColumns(hierarchy, columns, values, pivotKeys);
97+
if (hierarchy[pivotKeys.children]) {
98+
let flatCols = {};
99+
PivotUtil.flattenColumnHierarchy(hierarchy[pivotKeys.children], values, pivotKeys).forEach(o => {
100+
delete o[pivotKeys.records];
101+
flatCols = {...flatCols, ...o};
102+
});
103+
delete hierarchy[pivotKeys.children]; /* or we can keep it
104+
and use when creating the columns in pivot grid instead of recreating it */
105+
const keys = Object.keys(hierarchy);
106+
//remove all record keys from final data since we don't need them anymore.
107+
keys.forEach(k => {
108+
if (k.indexOf(pivotKeys.records) !== -1 || k === pivotKeys.level) {
109+
delete hierarchy[k];
110+
}
111+
});
112+
if (this.isLeaf(hierarchy, pivotKeys)) {
113+
delete hierarchy[pivotKeys.records]; /* remove the helper records of the actual records so that
114+
expand indicators can be rendered properly */
115+
}
116+
for (const property in flatCols) {
117+
if (flatCols.hasOwnProperty(property)) {
118+
hierarchy[property] = flatCols[property];
119+
}
120+
}
121+
result.push(hierarchy);
122+
}
123+
});
124+
return result;
125+
}
126+
127+
private groupColumns(hierarchy, columns, values, pivotKeys) {
128+
const children = hierarchy[pivotKeys.children];
129+
if (children) {
130+
this.groupColumns(children, columns, values, pivotKeys);
131+
} else if (hierarchy[pivotKeys.records]) {
132+
const leafRecords = this.getLeafs(hierarchy[pivotKeys.records], pivotKeys);
133+
hierarchy[pivotKeys.children] = PivotUtil.getFieldsHierarchy(leafRecords, columns, PivotDimensionType.Column, pivotKeys);
134+
PivotUtil.applyAggregations(hierarchy[pivotKeys.children], values, pivotKeys);
135+
}
136+
}
137+
138+
private getLeafs(records, pivotKeys) {
139+
let leafs = [];
140+
for (const rec of records) {
141+
if (rec[pivotKeys.records]) {
142+
leafs = leafs.concat(this.getLeafs(rec[pivotKeys.records], pivotKeys));
143+
} else {
144+
leafs.push(rec);
145+
}
146+
}
147+
return leafs;
148+
}
149+
150+
private isLeaf(record, pivotKeys) {
151+
return !(record[pivotKeys.records] && record[pivotKeys.records].some(r => r[pivotKeys.records]));
152+
}
153+
}

projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.component.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@
3232
class="igx-grid__scroll-on-drag-pinned" [style.left.px]="pinnedWidth"></span>
3333
<ng-template igxGridFor let-rowData [igxGridForOf]="data
3434
| pivotGridFilter:pivotConfiguration.filters:filterStrategy:advancedFilteringExpressionsTree
35-
| pivotGridRow:pivotConfiguration.rows:expansionStates:pivotConfiguration.values
36-
| pivotGridColumn:pivotConfiguration.columns:pivotConfiguration.values"
35+
| pivotGridRow:pivotConfiguration:expansionStates
36+
| pivotGridRowExpansion:pivotConfiguration:expansionStates
37+
| pivotGridColumn:pivotConfiguration:expansionStates"
3738
let-rowIndex="index" [igxForScrollOrientation]="'vertical'" [igxForScrollContainer]='verticalScroll'
3839
[igxForContainerSize]='calcHeight'
3940
[igxForItemSize]="hasColumnLayouts ? rowHeight * multiRowLayoutRowSize + 1 : renderedRowHeight"

projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.component.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ import { GridType } from '../common/grid.interface';
1818
import { IgxGridNavigationService } from '../grid-navigation.service';
1919
import { IgxGridCRUDService } from '../common/crud.service';
2020
import { IgxGridSummaryService } from '../summaries/grid-summary.service';
21-
import { IPivotConfiguration, PivotDimensionType } from './pivot-grid.interface';
21+
import { IPivotConfiguration, IPivotDimension, IPivotKeys, PivotDimensionType } from './pivot-grid.interface';
2222
import { IgxPivotHeaderRowComponent } from './pivot-header-row.component';
2323
import { IgxColumnGroupComponent } from '../columns/column-group.component';
2424
import { IgxColumnComponent } from '../columns/column.component';
2525
import { PivotUtil } from './pivot-util';
26+
import { NoopPivotDimensionsStrategy } from '../../data-operations/pivot-strategy';
2627

2728
let NEXT_ID = 0;
2829
const MINIMUM_COLUMN_WIDTH = 200;
@@ -81,6 +82,7 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
8182

8283

8384
public columnGroupStates = new Map<string, boolean>();
85+
public pivotKeys: IPivotKeys = {aggregations: 'aggregations', records: 'records', children: 'children', level: 'level'};
8486
public isPivot = true;
8587
protected _defaultExpandState = true;
8688
private _data;
@@ -267,15 +269,26 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
267269
/**
268270
* @hidden
269271
*/
270-
protected autogenerateColumns() {
271-
const data = this.gridAPI.get_data();
272-
const fieldsMap = PivotUtil.getFieldsHierarchy(
272+
protected autogenerateColumns() {
273+
let columns = [];
274+
const data = this.gridAPI.get_data();
275+
let fieldsMap;
276+
if (this.pivotConfiguration.columnStrategy && this.pivotConfiguration.columnStrategy instanceof NoopPivotDimensionsStrategy) {
277+
const fields = this.generateDataFields(data);
278+
const rowFields = PivotUtil.flatten(this.pivotConfiguration.rows).map(x => x.fieldName);
279+
const keyFields = Object.values(this.pivotKeys);
280+
const filteredFields = fields.filter(x => rowFields.indexOf(x) === -1 && keyFields.indexOf(x) === -1 &&
281+
x.indexOf('_level') === -1 && x.indexOf('_records') === -1);
282+
fieldsMap = this.generateFromData(filteredFields);
283+
} else {
284+
fieldsMap = PivotUtil.getFieldsHierarchy(
273285
data,
274286
this.pivotConfiguration.columns,
275287
PivotDimensionType.Column,
276288
{aggregations: 'aggregations', records: 'records', children: 'children', level: 'level'}
277289
);
278-
const columns = this.generateColumnHierarchy(fieldsMap, data);
290+
}
291+
columns = this.generateColumnHierarchy(fieldsMap, data);
279292
this._autoGeneratedCols = columns;
280293

281294
this.columnList.reset(columns);
@@ -284,7 +297,26 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
284297
}
285298
}
286299

287-
protected generateColumnHierarchy(fields: Map<string, any>, data, parent = null): IgxColumnComponent[] {
300+
protected generateFromData(fields: string[]) {
301+
const dataArr = fields.map(x => x.split('-')).sort(x => x.length);
302+
const hierarchy = new Map<string, any>();
303+
dataArr.forEach(arr => {
304+
let currentHierarchy = hierarchy;
305+
const path = [];
306+
for(const val of arr) {
307+
path.push(val);
308+
let h = currentHierarchy.get(path.join('-'));
309+
if(!h) {
310+
currentHierarchy.set(path.join('-'), { expandable: true, children: new Map<string, any>()});
311+
h = currentHierarchy.get(path.join('-'));
312+
}
313+
currentHierarchy = h.children;
314+
}
315+
});
316+
return hierarchy;
317+
}
318+
319+
protected generateColumnHierarchy(fields: Map<string, any>, data, parent = null): IgxColumnComponent[] {
288320
const factoryColumn = this.resolver.resolveComponentFactory(IgxColumnComponent);
289321
const factoryColumnGroup = this.resolver.resolveComponentFactory(IgxColumnGroupComponent);
290322
let columns = [];

projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.interface.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { FilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree';
2+
import { IPivotDimensionStrategy } from '../../data-operations/pivot-strategy';
3+
24

35
export interface IPivotConfiguration {
6+
rowStrategy?: IPivotDimensionStrategy | null;
7+
columnStrategy?: IPivotDimensionStrategy | null;
48
rows: IPivotDimension[] | null;
59
columns: IPivotDimension[] | null;
610
values: IPivotValue[] | null;
@@ -10,7 +14,7 @@ export interface IPivotConfiguration {
1014

1115
export interface IPivotDimension {
1216
// allow defining a hierarchy when multiple sub groups need to be extracted from single member.
13-
childLevels: IPivotDimension[];
17+
childLevels?: IPivotDimension[];
1418
// field name which to use to extract value or function that extract the value.
1519
member: string | ((data: any) => any);
1620
// Enables/Disables a particular dimension from pivot structure.

projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
22
import { IgxGridModule } from '../grid/grid.module';
33
import { IgxPivotGridComponent } from './pivot-grid.component';
44
import { IgxPivotRowComponent } from './pivot-row.component';
5-
import { IgxPivotRowPipe, IgxPivotColumnPipe, IgxPivotGridFilterPipe } from './pivot-grid.pipes';
5+
import { IgxPivotRowPipe, IgxPivotColumnPipe, IgxPivotGridFilterPipe, IgxPivotRowExpansionPipe } from './pivot-grid.pipes';
66
import { IgxGridComponent } from '../grid/grid.component';
77
import { IgxPivotHeaderRowComponent } from './pivot-header-row.component';
88

@@ -15,6 +15,7 @@ import { IgxPivotHeaderRowComponent } from './pivot-header-row.component';
1515
IgxPivotRowComponent,
1616
IgxPivotHeaderRowComponent,
1717
IgxPivotRowPipe,
18+
IgxPivotRowExpansionPipe,
1819
IgxPivotColumnPipe,
1920
IgxPivotGridFilterPipe
2021
],
@@ -23,6 +24,7 @@ import { IgxPivotHeaderRowComponent } from './pivot-header-row.component';
2324
IgxPivotGridComponent,
2425
IgxPivotRowComponent,
2526
IgxPivotHeaderRowComponent,
27+
IgxPivotRowExpansionPipe,
2628
IgxPivotRowPipe,
2729
IgxPivotColumnPipe,
2830
IgxPivotGridFilterPipe

0 commit comments

Comments
 (0)