Skip to content

Commit c015184

Browse files
authored
Merge pull request #10431 from IgniteUI/mkirova/sort-dimensions
Allow sorting dimension values via related chips.
2 parents 1c9e3e3 + 70dd881 commit c015184

File tree

10 files changed

+221
-51
lines changed

10 files changed

+221
-51
lines changed

projects/igniteui-angular/src/lib/data-operations/pivot-strategy.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { IgxPivotGridComponent } from '../grids/pivot-grid/pivot-grid.component'
44
import { IPivotDimension, IPivotKeys, IPivotValue, PivotDimensionType } from '../grids/pivot-grid/pivot-grid.interface';
55
import { PivotUtil } from '../grids/pivot-grid/pivot-util';
66
import { FilteringStrategy } from './filtering-strategy';
7+
import { GridColumnDataType } from './data-util';
8+
import { SortingDirection } from './sorting-expression.interface';
9+
import { DefaultSortingStrategy, ISortingStrategy } from './sorting-strategy';
10+
import { parseDate } from '../core/utils';
711

812
export interface IPivotDimensionStrategy {
913
process(collection: any,
@@ -173,3 +177,40 @@ export class DimensionValuesFilteringStrategy extends FilteringStrategy {
173177
return PivotUtil.extractValueFromDimension(dim, rec);
174178
}
175179
}
180+
181+
export class DefaultPivotSortingStrategy extends DefaultSortingStrategy {
182+
protected static _instance: DefaultPivotSortingStrategy = null;
183+
protected dimension;
184+
public static instance(): DefaultPivotSortingStrategy {
185+
return this._instance || (this._instance = new this());
186+
}
187+
public sort(data: any[],
188+
fieldName: string,
189+
dir: SortingDirection,
190+
ignoreCase: boolean,
191+
valueResolver: (obj: any, key: string, isDate?: boolean) => any,
192+
isDate?: boolean,
193+
isTime?: boolean,
194+
grid?: GridType) {
195+
const key = fieldName;
196+
const config = (grid as any).pivotConfiguration;
197+
const allDimensions = config.rows.concat(config.columns).concat(config.filters).filter(x => x !== null);
198+
const enabledDimensions = allDimensions.filter(x => x && x.enabled);
199+
this.dimension = PivotUtil.flatten(enabledDimensions).find(x => x.memberName === key);
200+
const reverse = (dir === SortingDirection.Desc ? -1 : 1);
201+
const cmpFunc = (obj1, obj2) => this.compareObjects(obj1, obj2, key, reverse, ignoreCase, this.getFieldValue, isDate, isTime);
202+
return this.arraySort(data, cmpFunc);
203+
}
204+
205+
protected getFieldValue(obj: any, key: string, isDate: boolean = false, isTime: boolean = false): any {
206+
let resolvedValue = PivotUtil.extractValueFromDimension(this.dimension, obj);
207+
const formatAsDate = this.dimension.dataType === GridColumnDataType.Date || this.dimension.dataType === GridColumnDataType.DateTime;
208+
if (formatAsDate) {
209+
const date = parseDate(resolvedValue);
210+
resolvedValue = isTime && date ?
211+
new Date().setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()) : date;
212+
213+
}
214+
return resolvedValue;
215+
}
216+
}

projects/igniteui-angular/src/lib/data-operations/sorting-strategy.ts

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@ const TIME_TYPE = 'time';
1313
const DATE_TIME_TYPE = 'dateTime';
1414
export interface ISortingStrategy {
1515
sort: (data: any[],
16-
fieldName: string,
17-
dir: SortingDirection,
18-
ignoreCase: boolean,
19-
valueResolver: (obj: any, key: string, isDate?: boolean) => any,
20-
isDate?: boolean,
21-
isTime?: boolean) => any[];
16+
fieldName: string,
17+
dir: SortingDirection,
18+
ignoreCase: boolean,
19+
valueResolver: (obj: any, key: string, isDate?: boolean) => any,
20+
isDate?: boolean,
21+
isTime?: boolean,
22+
grid?: GridType) => any[];
2223
}
2324

2425
export class DefaultSortingStrategy implements ISortingStrategy {
25-
private static _instance: DefaultSortingStrategy = null;
26+
protected static _instance: DefaultSortingStrategy = null;
2627

2728
protected constructor() {}
2829

@@ -31,12 +32,12 @@ export class DefaultSortingStrategy implements ISortingStrategy {
3132
}
3233

3334
public sort(data: any[],
34-
fieldName: string,
35-
dir: SortingDirection,
36-
ignoreCase: boolean,
37-
valueResolver: (obj: any, key: string, isDate?: boolean) => any,
38-
isDate?: boolean,
39-
isTime?: boolean) {
35+
fieldName: string,
36+
dir: SortingDirection,
37+
ignoreCase: boolean,
38+
valueResolver: (obj: any, key: string, isDate?: boolean) => any,
39+
isDate?: boolean,
40+
isTime?: boolean) {
4041
const key = fieldName;
4142
const reverse = (dir === SortingDirection.Desc ? -1 : 1);
4243
const cmpFunc = (obj1, obj2) => this.compareObjects(obj1, obj2, key, reverse, ignoreCase, valueResolver, isDate, isTime);
@@ -65,8 +66,8 @@ export class DefaultSortingStrategy implements ISortingStrategy {
6566
valueResolver: (obj: any, key: string, isDate?: boolean, isTime?: boolean) => any,
6667
isDate: boolean,
6768
isTime: boolean) {
68-
let a = valueResolver(obj1, key, isDate, isTime);
69-
let b = valueResolver(obj2, key, isDate, isTime);
69+
let a = valueResolver.call(this, obj1, key, isDate, isTime);
70+
let b = valueResolver.call(this, obj2, key, isDate, isTime);
7071
if (ignoreCase) {
7172
a = a && a.toLowerCase ? a.toLowerCase() : a;
7273
b = b && b.toLowerCase ? b.toLowerCase() : b;
@@ -163,38 +164,17 @@ export class IgxSorting implements IGridSortingStrategy {
163164
let resolvedValue = resolveNestedPath(obj, key);
164165
if (isDate || isTime) {
165166
const date = parseDate(resolvedValue);
166-
resolvedValue = isTime && date ?
167+
resolvedValue = isTime && date ?
167168
new Date().setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()) : date;
168169

169170
}
170171
return resolvedValue;
171172
}
172173

173-
private groupedRecordsByExpression(data: any[],
174-
index: number,
175-
expression: IGroupingExpression,
176-
isDate: boolean = false): any[] {
177-
const res = [];
178-
const key = expression.fieldName;
179-
const len = data.length;
180-
const groupval = this.getFieldValue(data[index], key, isDate);
181-
res.push(data[index]);
182-
index++;
183-
const comparer = expression.groupingComparer || DefaultSortingStrategy.instance().compareValues;
184-
for (let i = index; i < len; i++) {
185-
if (comparer(this.getFieldValue(data[i], key, isDate), groupval) === 0) {
186-
res.push(data[i]);
187-
} else {
188-
break;
189-
}
190-
}
191-
return res;
192-
}
193-
194-
private sortDataRecursive<T>(data: T[],
195-
expressions: ISortingExpression[],
196-
expressionIndex: number = 0,
197-
grid: GridType): T[] {
174+
protected sortDataRecursive<T>(data: T[],
175+
expressions: ISortingExpression[],
176+
expressionIndex: number = 0,
177+
grid: GridType): T[] {
198178
let i;
199179
let j;
200180
let gbData;
@@ -212,7 +192,7 @@ export class IgxSorting implements IGridSortingStrategy {
212192
const column = grid?.getColumnByName(expr.fieldName);
213193
const isDate = column?.dataType === DATE_TYPE || column?.dataType === DATE_TIME_TYPE;
214194
const isTime = column?.dataType === TIME_TYPE;
215-
data = expr.strategy.sort(data, expr.fieldName, expr.dir, expr.ignoreCase, this.getFieldValue, isDate, isTime);
195+
data = expr.strategy.sort(data, expr.fieldName, expr.dir, expr.ignoreCase, this.getFieldValue, isDate, isTime, grid);
216196
if (expressionIndex === exprsLen - 1) {
217197
return data;
218198
}
@@ -230,6 +210,27 @@ export class IgxSorting implements IGridSortingStrategy {
230210
}
231211
return data;
232212
}
213+
214+
private groupedRecordsByExpression(data: any[],
215+
index: number,
216+
expression: IGroupingExpression,
217+
isDate: boolean = false): any[] {
218+
const res = [];
219+
const key = expression.fieldName;
220+
const len = data.length;
221+
const groupval = this.getFieldValue(data[index], key, isDate);
222+
res.push(data[index]);
223+
index++;
224+
const comparer = expression.groupingComparer || DefaultSortingStrategy.instance().compareValues;
225+
for (let i = index; i < len; i++) {
226+
if (comparer(this.getFieldValue(data[i], key, isDate), groupval) === 0) {
227+
res.push(data[i]);
228+
} else {
229+
break;
230+
}
231+
}
232+
return res;
233+
}
233234
}
234235

235236
export class IgxDataRecordSorting extends IgxSorting {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
class="igx-grid__scroll-on-drag-pinned" [style.left.px]="pinnedWidth"></span>
3232
<ng-template igxGridFor let-rowData [igxGridForOf]="data
3333
| pivotGridFilter:pivotConfiguration:filterStrategy:advancedFilteringExpressionsTree:filteringPipeTrigger:pipeTrigger
34+
| pivotGridSort:pivotConfiguration:sortStrategy:id:pipeTrigger
3435
| pivotGridRow:pivotConfiguration:expansionStates:pipeTrigger
3536
| pivotGridRowExpansion:pivotConfiguration:expansionStates:pipeTrigger
3637
| pivotGridColumn:pivotConfiguration:expansionStates:pipeTrigger"

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import { IgxGridTransaction } from '../hierarchical-grid/public_api';
6060
import { IgxPivotFilteringService } from './pivot-filtering.service';
6161
import { DataUtil } from '../../data-operations/data-util';
6262
import { IFilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree';
63+
import { SortingDirection } from '../../data-operations/sorting-expression.interface';
6364

6465
let NEXT_ID = 0;
6566
const MINIMUM_COLUMN_WIDTH = 200;
@@ -958,7 +959,17 @@ export class IgxPivotGridComponent extends IgxGridBaseDirective implements OnIni
958959
const factoryColumn = this.resolver.resolveComponentFactory(IgxColumnComponent);
959960
const factoryColumnGroup = this.resolver.resolveComponentFactory(IgxColumnGroupComponent);
960961
let columns = [];
961-
fields.forEach((value, key) => {
962+
if (fields.size === 0) {
963+
return columns;
964+
}
965+
const first = fields.keys().next().value;
966+
const dim: IPivotDimension = fields.get(first).dimension;
967+
let currentFields = fields;
968+
if (dim && dim.sortDirection) {
969+
const reverse = (dim.sortDirection === SortingDirection.Desc ? -1 : 1);
970+
currentFields = new Map([...fields.entries()].sort((a, b) => reverse * (a > b ? 1 : a < b ? -1 : 0)));
971+
}
972+
currentFields.forEach((value, key) => {
962973
let shouldGenerate = true;
963974
if (value.dimension && value.dimension.filters) {
964975
const state = {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { GridColumnDataType } from '../../data-operations/data-util';
22
import { FilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree';
33
import { IPivotDimensionStrategy } from '../../data-operations/pivot-strategy';
44
import { IgxColumnComponent } from '../columns/column.component';
5+
import { SortingDirection } from '../../data-operations/sorting-expression.interface';
56

67
export type PivotAggregation = (members: any[], data: any[]) => any;
78

@@ -26,6 +27,8 @@ export interface IPivotDimension {
2627
enabled: boolean;
2728
// A predefined or defined via the `igxPivotSelector` filter expression tree for the current dimension to be applied in the filter pipe.
2829
filter?: FilteringExpressionsTree | null;
30+
sortDirection?: SortingDirection;
31+
dataType?: GridColumnDataType;
2932
}
3033

3134
export interface IPivotValue {

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ 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, IgxPivotRowExpansionPipe } from './pivot-grid.pipes';
5+
import { IgxPivotRowPipe, IgxPivotColumnPipe, IgxPivotGridFilterPipe,
6+
IgxPivotRowExpansionPipe, IgxPivotGridSortingPipe } from './pivot-grid.pipes';
67
import { IgxGridComponent } from '../grid/grid.component';
78
import { IgxPivotHeaderRowComponent } from './pivot-header-row.component';
89
import { IgxPivotRowDimensionContentComponent } from './pivot-row-dimension-content.component';
@@ -19,7 +20,8 @@ import { IgxPivotRowDimensionContentComponent } from './pivot-row-dimension-cont
1920
IgxPivotRowPipe,
2021
IgxPivotRowExpansionPipe,
2122
IgxPivotColumnPipe,
22-
IgxPivotGridFilterPipe
23+
IgxPivotGridFilterPipe,
24+
IgxPivotGridSortingPipe
2325
],
2426
exports: [
2527
IgxGridModule,
@@ -30,7 +32,8 @@ import { IgxPivotRowDimensionContentComponent } from './pivot-row-dimension-cont
3032
IgxPivotRowExpansionPipe,
3133
IgxPivotRowPipe,
3234
IgxPivotColumnPipe,
33-
IgxPivotGridFilterPipe
35+
IgxPivotGridFilterPipe,
36+
IgxPivotGridSortingPipe
3437
],
3538
imports: [
3639
IgxGridModule,

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import { DataUtil } from '../../data-operations/data-util';
44
import { FilteringExpressionsTree, IFilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree';
55
import { IFilteringStrategy } from '../../data-operations/filtering-strategy';
66
import { IPivotConfiguration, IPivotDimension, IPivotKeys } from './pivot-grid.interface';
7-
import { DimensionValuesFilteringStrategy, PivotColumnDimensionsStrategy,
7+
import { DefaultPivotSortingStrategy, DimensionValuesFilteringStrategy, PivotColumnDimensionsStrategy,
88
PivotRowDimensionsStrategy } from '../../data-operations/pivot-strategy';
99
import { PivotUtil } from './pivot-util';
1010
import { FilteringLogic } from '../../data-operations/filtering-expression.interface';
11+
import { IGridSortingStrategy } from '../../data-operations/sorting-strategy';
12+
import { ISortingExpression, SortingDirection } from '../../data-operations/sorting-expression.interface';
1113
import { GridBaseAPIService, IgxGridBaseDirective } from '../hierarchical-grid/public_api';
1214
import { GridType } from '../common/grid.interface';
1315
/**
@@ -131,3 +133,43 @@ export class IgxPivotGridFilterPipe implements PipeTransform {
131133
return result;
132134
}
133135
}
136+
137+
/**
138+
* @hidden
139+
*/
140+
@Pipe({
141+
name: 'pivotGridSort',
142+
pure: true
143+
})
144+
export class IgxPivotGridSortingPipe implements PipeTransform {
145+
constructor(private gridAPI: GridBaseAPIService<IgxGridBaseDirective & GridType>) { }
146+
public transform(collection: any[], config: IPivotConfiguration, sorting: IGridSortingStrategy,
147+
id: string, pipeTrigger: number, pinned?): any[] {
148+
let result: any[];
149+
const allDimensions = config.rows;
150+
const enabledDimensions = allDimensions.filter(x => x && x.enabled);
151+
const expressions: ISortingExpression[] = [];
152+
PivotUtil.flatten(enabledDimensions).forEach(x => {
153+
if (x.sortDirection) {
154+
expressions.push({
155+
dir: x.sortDirection,
156+
fieldName: x.memberName,
157+
strategy: DefaultPivotSortingStrategy.instance()
158+
});
159+
} else {
160+
expressions.push({
161+
dir: SortingDirection.None,
162+
fieldName: x.memberName,
163+
strategy: DefaultPivotSortingStrategy.instance()
164+
});
165+
}
166+
});
167+
if (!expressions.length) {
168+
result = collection;
169+
} else {
170+
result = DataUtil.sort(cloneArray(collection, true), expressions, sorting, this.gridAPI.grid);
171+
}
172+
173+
return result;
174+
}
175+
}

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,48 @@ describe('Basic IgxPivotGrid #pivotGrid', () => {
118118
const expected = ['USA'];
119119
expect(colHeaders).toEqual(expected);
120120
});
121+
122+
it('should apply sorting for dimension via row chip', () => {
123+
fixture.detectChanges();
124+
const pivotGrid = fixture.componentInstance.pivotGrid;
125+
const headerRow = fixture.nativeElement.querySelector('igx-pivot-header-row');
126+
const rowChip = headerRow.querySelector('igx-chip[id="All"]');
127+
rowChip.click();
128+
fixture.detectChanges();
129+
let rows = pivotGrid.rowList.toArray();
130+
let expectedOrder = ['All', 'Accessories', 'Bikes', 'Clothing', 'Components'];
131+
let rowDimensionHeaders = rows.map(x => x.rowDimension).flat().map(x => x.header);
132+
expect(rowDimensionHeaders).toEqual(expectedOrder);
133+
134+
rowChip.click();
135+
fixture.detectChanges();
136+
rows = pivotGrid.rowList.toArray();
137+
expectedOrder = ['All', 'Components', 'Clothing', 'Bikes', 'Accessories'];
138+
rowDimensionHeaders = rows.map(x => x.rowDimension).flat().map(x => x.header);
139+
expect(rowDimensionHeaders).toEqual(expectedOrder);
140+
});
141+
142+
it('should apply sorting for dimension via column chip', () => {
143+
const pivotGrid = fixture.componentInstance.pivotGrid;
144+
const headerRow = fixture.nativeElement.querySelector('igx-pivot-header-row');
145+
const colChip = headerRow.querySelector('igx-chip[id="Country"]');
146+
147+
// sort
148+
colChip.click();
149+
fixture.detectChanges();
150+
151+
let colHeaders = pivotGrid.columns.filter(x => x.level === 0).map(x => x.header);
152+
let expected = ['Bulgaria', 'USA', 'Uruguay'];
153+
expect(colHeaders).toEqual(expected);
154+
155+
// sort
156+
colChip.click();
157+
fixture.detectChanges();
158+
159+
colHeaders = pivotGrid.columns.filter(x => x.level === 0).map(x => x.header);
160+
expected = ['Uruguay', 'USA', 'Bulgaria'];
161+
expect(colHeaders).toEqual(expected);
162+
});
121163
});
122164
});
165+

0 commit comments

Comments
 (0)