@adaptabletools/adaptable 20.0.4 → 20.0.5-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/AdaptableState/Common/AggregationColumns.d.ts +8 -1
- package/src/AdaptableState/LayoutState.d.ts +12 -0
- package/src/Api/Implementation/ColumnApiImpl.d.ts +1 -0
- package/src/Api/Implementation/ColumnApiImpl.js +11 -1
- package/src/Api/Implementation/LayoutHelpers.js +25 -2
- package/src/Utilities/Constants/GeneralConstants.d.ts +1 -0
- package/src/Utilities/Constants/GeneralConstants.js +1 -0
- package/src/env.js +2 -2
- package/src/layout-manager/src/LayoutManagerModel.d.ts +23 -20
- package/src/layout-manager/src/index.d.ts +18 -1
- package/src/layout-manager/src/index.js +288 -2
- package/src/layout-manager/src/normalizeLayoutModel.js +3 -0
- package/tsconfig.esm.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adaptabletools/adaptable",
|
|
3
|
-
"version": "20.0.
|
|
3
|
+
"version": "20.0.5-canary.0",
|
|
4
4
|
"description": "Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web-components",
|
|
@@ -12,7 +12,14 @@ export type TableAggregationColumns = {
|
|
|
12
12
|
/**
|
|
13
13
|
* Defines an Aggregated Column in a Pivot Layout
|
|
14
14
|
*/
|
|
15
|
-
export type PivotAggregationColumns =
|
|
15
|
+
export type PivotAggregationColumns = {
|
|
16
|
+
ColumnId: string;
|
|
17
|
+
AggFunc: AggregationColumnValue;
|
|
18
|
+
TotalColumn?: boolean | 'before' | 'after' | {
|
|
19
|
+
PivotColumnId: string;
|
|
20
|
+
ShowTotal?: boolean | 'before' | 'after';
|
|
21
|
+
}[];
|
|
22
|
+
}[];
|
|
16
23
|
export declare const WEIGHTED_AVERAGE_AGG_FN_NAME = "weightedAvg";
|
|
17
24
|
/**
|
|
18
25
|
* Defines a Weighted Average Agg
|
|
@@ -148,6 +148,18 @@ export interface PivotLayout extends LayoutBase {
|
|
|
148
148
|
* Row Grouped Columns Columns - must NOT be provided
|
|
149
149
|
*/
|
|
150
150
|
RowGroupedColumns?: never;
|
|
151
|
+
/**
|
|
152
|
+
* Display Grand Total Row at the top or bottom of the Pivot Table
|
|
153
|
+
*/
|
|
154
|
+
GrandTotalRow?: 'top' | 'bottom' | boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Display automatically calculated Totals of all Pivot Columns, in the position specified
|
|
157
|
+
*/
|
|
158
|
+
GrandTotalColumn?: 'before' | 'after' | boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Display automatically calculated Totals within EACH Pivot Column Group, in the position specified
|
|
161
|
+
*/
|
|
162
|
+
PivotGroupTotalColumn?: 'before' | 'after' | boolean;
|
|
151
163
|
}
|
|
152
164
|
/**
|
|
153
165
|
* Manages how (and which) Row Group values are stored
|
|
@@ -11,6 +11,7 @@ export declare function generateAutoTreeSingleColumn(): AdaptableColumn;
|
|
|
11
11
|
export declare function generateAutoRowGroupColumnForColumn(column: AdaptableColumn): AdaptableColumn;
|
|
12
12
|
export declare function getFriendlyNameForPivotResultColumn(columnId: string): string;
|
|
13
13
|
export declare function isPivotResultColumn(columnId: string): boolean;
|
|
14
|
+
export declare function isPivotGrandTotalColumn(columnId: string): boolean;
|
|
14
15
|
export declare class ColumnApiImpl extends ApiBase implements ColumnApi {
|
|
15
16
|
internalApi: ColumnInternalApi;
|
|
16
17
|
constructor(_adaptable: IAdaptable);
|
|
@@ -63,6 +63,13 @@ export function generateAutoRowGroupColumnForColumn(column) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
export function getFriendlyNameForPivotResultColumn(columnId) {
|
|
66
|
+
if (isPivotGrandTotalColumn(columnId)) {
|
|
67
|
+
return `[Grand Total] ${columnId
|
|
68
|
+
.split('_')
|
|
69
|
+
.slice(2)
|
|
70
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
71
|
+
.join(' ')}`;
|
|
72
|
+
}
|
|
66
73
|
return `[Pivot] ${columnId
|
|
67
74
|
.split('_')
|
|
68
75
|
.slice(1)
|
|
@@ -73,6 +80,9 @@ export function isPivotResultColumn(columnId) {
|
|
|
73
80
|
// put this here as there might be other indicators we are not aware of?
|
|
74
81
|
return columnId?.startsWith(GeneralConstants.AG_GRID_PIVOT_COLUMN);
|
|
75
82
|
}
|
|
83
|
+
export function isPivotGrandTotalColumn(columnId) {
|
|
84
|
+
return columnId?.startsWith(GeneralConstants.AG_GRID_PIVOT_GRAND_TOTAL_COLUMN);
|
|
85
|
+
}
|
|
76
86
|
export class ColumnApiImpl extends ApiBase {
|
|
77
87
|
constructor(_adaptable) {
|
|
78
88
|
super(_adaptable);
|
|
@@ -136,7 +146,7 @@ export class ColumnApiImpl extends ApiBase {
|
|
|
136
146
|
return isAutoRowGroupColumn(columnId);
|
|
137
147
|
}
|
|
138
148
|
isPivotResultColumn(columnId) {
|
|
139
|
-
return isPivotResultColumn(columnId);
|
|
149
|
+
return isPivotResultColumn(columnId) || isPivotGrandTotalColumn(columnId);
|
|
140
150
|
}
|
|
141
151
|
isAutoRowGroupColumnForSingle(columnId) {
|
|
142
152
|
return columnId === GROUP_COLUMN_ID__SINGLE;
|
|
@@ -197,12 +197,16 @@ export const pivotLayoutToPivotLayoutModel = (pivotLayout) => {
|
|
|
197
197
|
ColumnPinning: pivotLayout.ColumnPinning,
|
|
198
198
|
PivotColumns: pivotLayout.PivotColumns,
|
|
199
199
|
PivotExpandLevel: pivotLayout.PivotExpandLevel,
|
|
200
|
-
PivotAggregationColumns: (pivotLayout.PivotAggregationColumns || []).map(({ ColumnId, AggFunc }) => {
|
|
200
|
+
PivotAggregationColumns: (pivotLayout.PivotAggregationColumns || []).map(({ ColumnId, AggFunc, TotalColumn }) => {
|
|
201
201
|
return {
|
|
202
202
|
ColumnId,
|
|
203
203
|
AggFunc: toAggFunc(AggFunc),
|
|
204
|
+
TotalColumn,
|
|
204
205
|
};
|
|
205
206
|
}),
|
|
207
|
+
GrandTotalRow: pivotLayout.GrandTotalRow,
|
|
208
|
+
GrandTotalColumn: pivotLayout.GrandTotalColumn,
|
|
209
|
+
PivotGroupTotalColumn: pivotLayout.PivotGroupTotalColumn,
|
|
206
210
|
RowGroupValues: pivotLayout.RowGroupValues
|
|
207
211
|
? pivotLayout.RowGroupValues.RowGroupDefaultBehavior === 'always-collapsed' ||
|
|
208
212
|
pivotLayout.RowGroupValues.RowGroupDefaultBehavior === 'always-expanded'
|
|
@@ -326,10 +330,29 @@ export const pivotLayoutModelToPivotLayout = (layoutModel, defaults) => {
|
|
|
326
330
|
else {
|
|
327
331
|
delete pivotLayout.RowGroupValues;
|
|
328
332
|
}
|
|
333
|
+
if (layoutModel.GrandTotalRow) {
|
|
334
|
+
pivotLayout.GrandTotalRow = layoutModel.GrandTotalRow;
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
delete pivotLayout.GrandTotalRow;
|
|
338
|
+
}
|
|
339
|
+
if (layoutModel.GrandTotalColumn) {
|
|
340
|
+
pivotLayout.GrandTotalColumn = layoutModel.GrandTotalColumn;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
delete pivotLayout.GrandTotalColumn;
|
|
344
|
+
}
|
|
345
|
+
if (layoutModel.PivotGroupTotalColumn) {
|
|
346
|
+
pivotLayout.PivotGroupTotalColumn = layoutModel.PivotGroupTotalColumn;
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
delete pivotLayout.PivotGroupTotalColumn;
|
|
350
|
+
}
|
|
329
351
|
if (layoutModel.PivotAggregationColumns) {
|
|
330
|
-
pivotLayout.PivotAggregationColumns = (layoutModel.PivotAggregationColumns || []).map(({ ColumnId, AggFunc }) => ({
|
|
352
|
+
pivotLayout.PivotAggregationColumns = (layoutModel.PivotAggregationColumns || []).map(({ ColumnId, AggFunc, TotalColumn }) => ({
|
|
331
353
|
ColumnId,
|
|
332
354
|
AggFunc: toAggregationColumnValue(AggFunc),
|
|
355
|
+
TotalColumn,
|
|
333
356
|
}));
|
|
334
357
|
}
|
|
335
358
|
else {
|
|
@@ -24,6 +24,7 @@ export declare const GROUP_PATH_SEPARATOR: string;
|
|
|
24
24
|
export declare const AG_GRID_GROUPED_COLUMN: string;
|
|
25
25
|
export declare const AG_GRID_SELECTION_COLUMN: string;
|
|
26
26
|
export declare const AG_GRID_PIVOT_COLUMN: string;
|
|
27
|
+
export declare const AG_GRID_PIVOT_GRAND_TOTAL_COLUMN: string;
|
|
27
28
|
export declare const AG_GRID_CHART_WINDOW = "AG Grid Window";
|
|
28
29
|
export declare const ADAPTABLE_FDC3_ACTION_COLUMN_FRIENDLY_NAME = "(FDC3ActionColumn)";
|
|
29
30
|
export declare const DEFAULT_DATE_FORMAT_PATTERN = "dd-MM-yyyy";
|
|
@@ -23,6 +23,7 @@ export const GROUP_PATH_SEPARATOR = '/';
|
|
|
23
23
|
export const AG_GRID_GROUPED_COLUMN = 'ag-Grid-AutoColumn';
|
|
24
24
|
export const AG_GRID_SELECTION_COLUMN = 'ag-Grid-SelectionColumn';
|
|
25
25
|
export const AG_GRID_PIVOT_COLUMN = 'pivot_';
|
|
26
|
+
export const AG_GRID_PIVOT_GRAND_TOTAL_COLUMN = 'PivotRowTotal_pivot_';
|
|
26
27
|
export const AG_GRID_CHART_WINDOW = 'AG Grid Window';
|
|
27
28
|
export const ADAPTABLE_FDC3_ACTION_COLUMN_FRIENDLY_NAME = '(FDC3ActionColumn)';
|
|
28
29
|
// FIXME AFL - load this from DateInputOptions
|
package/src/env.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
NEXT_PUBLIC_INFINITE_TABLE_LICENSE_KEY: "StartDate=2021-06-29|EndDate=2030-01-01|Owner=Adaptable|Type=distribution|TS=1624971462479|C=137829811,1004007071,2756196225,1839832928,3994409405,636616862" || '',
|
|
3
|
-
PUBLISH_TIMESTAMP:
|
|
4
|
-
VERSION: "20.0.
|
|
3
|
+
PUBLISH_TIMESTAMP: 1744819271502 || Date.now(),
|
|
4
|
+
VERSION: "20.0.5-canary.0" || '--current-version--',
|
|
5
5
|
};
|
|
@@ -62,26 +62,17 @@ export type ColumnAggregationModel = {
|
|
|
62
62
|
aggFunc: string | true;
|
|
63
63
|
weightedColumnId?: string;
|
|
64
64
|
};
|
|
65
|
-
export type
|
|
65
|
+
export type AggregationColumnsModelItem = {
|
|
66
66
|
ColumnId: string;
|
|
67
67
|
AggFunc: ColumnAggregationModel;
|
|
68
|
-
}
|
|
69
|
-
type
|
|
70
|
-
export
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* Map of Columns with Summary Expressions
|
|
77
|
-
*/
|
|
78
|
-
ColumnsMap: Record<string, string>;
|
|
79
|
-
/**
|
|
80
|
-
* Evaluates only currently filtered rows in the summary
|
|
81
|
-
* @defaultValue true
|
|
82
|
-
*/
|
|
83
|
-
IncludeOnlyFilteredRows?: boolean;
|
|
84
|
-
}
|
|
68
|
+
};
|
|
69
|
+
export type AggregationColumnsModel = AggregationColumnsModelItem[];
|
|
70
|
+
export type PivotAggregationColumnsModel = (AggregationColumnsModelItem & {
|
|
71
|
+
TotalColumn?: boolean | 'before' | 'after' | {
|
|
72
|
+
PivotColumnId: string;
|
|
73
|
+
ShowTotal?: boolean | 'before' | 'after';
|
|
74
|
+
}[];
|
|
75
|
+
})[];
|
|
85
76
|
export interface TableLayoutModel extends BaseLayoutModel {
|
|
86
77
|
TableColumns: string[];
|
|
87
78
|
/**
|
|
@@ -96,6 +87,7 @@ export interface TableLayoutModel extends BaseLayoutModel {
|
|
|
96
87
|
* @defaultValue 'single'
|
|
97
88
|
*/
|
|
98
89
|
RowGroupDisplayType?: 'single' | 'multi';
|
|
90
|
+
PivotAggregationColumns?: never;
|
|
99
91
|
PivotColumns?: never;
|
|
100
92
|
PivotGroupedColumns?: never;
|
|
101
93
|
PivotExpandLevel?: never;
|
|
@@ -110,13 +102,24 @@ export interface PivotLayoutModel extends BaseLayoutModel {
|
|
|
110
102
|
/**
|
|
111
103
|
* Columns showing aggregated values in Group Rows; 1st value in record is Column name, 2nd is either aggfunc (e.g. sum, avg etc.) or 'true' (to use default aggfunc)
|
|
112
104
|
*/
|
|
113
|
-
PivotAggregationColumns?:
|
|
105
|
+
PivotAggregationColumns?: PivotAggregationColumnsModel;
|
|
114
106
|
TableAggregationColumns?: never;
|
|
115
107
|
/**
|
|
116
108
|
* Columns which are row-grouped when the Layout is applied
|
|
117
109
|
*/
|
|
118
110
|
PivotGroupedColumns?: string[];
|
|
119
111
|
RowGroupedColumns?: never;
|
|
112
|
+
/**
|
|
113
|
+
* Display Grand Total Row at the top or bottom of the Pivot Table
|
|
114
|
+
*/
|
|
115
|
+
GrandTotalRow?: 'top' | 'bottom' | boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Display Total of all Pivot Columns before or after the Pivot Columns
|
|
118
|
+
*/
|
|
119
|
+
GrandTotalColumn?: 'before' | 'after' | boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Display automatically calculated Totals within EACH Pivot Column Group, in the position specified
|
|
122
|
+
*/
|
|
123
|
+
PivotGroupTotalColumn?: 'before' | 'after' | boolean;
|
|
120
124
|
}
|
|
121
125
|
export type LayoutModel = TableLayoutModel | PivotLayoutModel;
|
|
122
|
-
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ColumnState, GridApi, GridOptions, GridState } from 'ag-grid-enterprise';
|
|
1
|
+
import { ColDef, ColumnState, GridApi, GridOptions, GridState } from 'ag-grid-enterprise';
|
|
2
2
|
import { PivotLayoutModel, TableLayoutModel } from './LayoutManagerModel';
|
|
3
3
|
import { LMEmitter } from './LMEmitter';
|
|
4
4
|
export type LayoutManagerOptions = {
|
|
@@ -69,7 +69,24 @@ export declare class LayoutManager<DATA_TYPE = any> extends LMEmitter {
|
|
|
69
69
|
private computeColumnOrderAndVisibility;
|
|
70
70
|
autoSizeColumns(columnIds?: string[]): false | string[];
|
|
71
71
|
private applyPivotLayout;
|
|
72
|
+
applyPivotTotals(layout: PivotLayoutModel): void;
|
|
72
73
|
applyPivotExpandLevel(layout: PivotLayoutModel): void;
|
|
73
74
|
private withSuppressColumnAnimation;
|
|
75
|
+
private setupPivotTotals;
|
|
76
|
+
private isPivotRowTotalColDef;
|
|
77
|
+
private patchGrandTotalColumn;
|
|
78
|
+
private isPivotGroupTotalColumn;
|
|
79
|
+
private patchPivotGroupTotalColumn;
|
|
80
|
+
private patchPivotTotalColumn;
|
|
81
|
+
destructurePivotColumnId(colDef: ColDef, currentModel: {
|
|
82
|
+
pivotColIds: string[];
|
|
83
|
+
aggColIds: string[];
|
|
84
|
+
}): '!unknown!' | {
|
|
85
|
+
pivotColumnIds: string[];
|
|
86
|
+
pivotKeys: string[];
|
|
87
|
+
pivotColumnId: string;
|
|
88
|
+
aggregationColumnId?: string;
|
|
89
|
+
};
|
|
90
|
+
private getPivotTotalColumnConfig;
|
|
74
91
|
}
|
|
75
92
|
export {};
|
|
@@ -3,7 +3,7 @@ import { isPivotLayoutModel } from './isPivotLayoutModel';
|
|
|
3
3
|
import { LMEmitter } from './LMEmitter';
|
|
4
4
|
import { GROUP_COLUMN_ID__MULTI_PREFIX, GROUP_COLUMN_ID__SINGLE, normalizeLayoutModel, normalizePivotLayoutModel, normalizeTableLayoutModel, } from './normalizeLayoutModel';
|
|
5
5
|
import { isLayoutEqual } from './isLayoutEqual';
|
|
6
|
-
import {
|
|
6
|
+
import { simplifyLayoutModel, simplifyPivotLayoutModel, simplifyTableLayoutModel, } from './simplifyLayoutModel';
|
|
7
7
|
import { sortColumnIdsByOrder } from './sortColumnIdsByOrder';
|
|
8
8
|
function flattenColDefs(colDefs) {
|
|
9
9
|
const res = [];
|
|
@@ -117,8 +117,12 @@ export class LayoutManager extends LMEmitter {
|
|
|
117
117
|
}).bind(this);
|
|
118
118
|
}
|
|
119
119
|
this.setOptions(options);
|
|
120
|
+
// this ensures the grand total columns are positioned correctly (before/after) even when changed at runtime
|
|
121
|
+
// see https://www.ag-grid.com/react-data-grid/pivoting-column-groups/#changing-data-filters-and-configurations
|
|
122
|
+
this.gridApi.setGridOption('enableStrictPivotColumnOrder', true);
|
|
120
123
|
this.setupEvents();
|
|
121
124
|
this.indexColumns();
|
|
125
|
+
this.setupPivotTotals();
|
|
122
126
|
globalThis.layoutManager = this;
|
|
123
127
|
}
|
|
124
128
|
destroy() {
|
|
@@ -252,6 +256,18 @@ export class LayoutManager extends LMEmitter {
|
|
|
252
256
|
PivotAggregationColumns: layout.TableAggregationColumns,
|
|
253
257
|
PivotExpandLevel: prevLayout?.PivotExpandLevel ?? -1,
|
|
254
258
|
};
|
|
259
|
+
const grandTotalRow = this.gridApi.getGridOption('grandTotalRow');
|
|
260
|
+
if (grandTotalRow) {
|
|
261
|
+
pivotLayout.GrandTotalRow = grandTotalRow;
|
|
262
|
+
}
|
|
263
|
+
const grandTotalColumn = this.gridApi.getGridOption('pivotRowTotals');
|
|
264
|
+
if (grandTotalColumn) {
|
|
265
|
+
pivotLayout.GrandTotalColumn = grandTotalColumn;
|
|
266
|
+
}
|
|
267
|
+
const pivotGroupTotalColumn = this.gridApi.getGridOption('pivotColumnGroupTotals');
|
|
268
|
+
if (pivotGroupTotalColumn) {
|
|
269
|
+
pivotLayout.PivotGroupTotalColumn = pivotGroupTotalColumn;
|
|
270
|
+
}
|
|
255
271
|
return simplifyPivotLayoutModel(pivotLayout);
|
|
256
272
|
}
|
|
257
273
|
getTableLayoutModelFromGrid() {
|
|
@@ -272,7 +288,7 @@ export class LayoutManager extends LMEmitter {
|
|
|
272
288
|
let ColumnPinning = {};
|
|
273
289
|
const gridState = this.gridApi.getState();
|
|
274
290
|
const prevLayout = this.currentLayout;
|
|
275
|
-
const prevAggColumns = prevLayout?.TableAggregationColumns;
|
|
291
|
+
const prevAggColumns = prevLayout?.TableAggregationColumns ?? prevLayout?.PivotAggregationColumns;
|
|
276
292
|
const prevAggColumnsMap = prevAggColumns?.reduce((acc, agg) => {
|
|
277
293
|
acc[agg.ColumnId] = agg;
|
|
278
294
|
return acc;
|
|
@@ -983,6 +999,7 @@ export class LayoutManager extends LMEmitter {
|
|
|
983
999
|
return columnIds;
|
|
984
1000
|
}
|
|
985
1001
|
applyPivotLayout(layout, options) {
|
|
1002
|
+
this.applyPivotTotals(layout);
|
|
986
1003
|
const columnState = this.computeColumnStateForPivotLayout(layout);
|
|
987
1004
|
// by simply calling this.gridApi.applyColumnState(columnState)
|
|
988
1005
|
// the order of aggregations is not preserved/guaranteed by ag-grid
|
|
@@ -1010,6 +1027,50 @@ export class LayoutManager extends LMEmitter {
|
|
|
1010
1027
|
this.applyRowGroupValues(layout.RowGroupValues);
|
|
1011
1028
|
}
|
|
1012
1029
|
}
|
|
1030
|
+
applyPivotTotals(layout) {
|
|
1031
|
+
/**
|
|
1032
|
+
* GrandTotalRow
|
|
1033
|
+
*/
|
|
1034
|
+
if (layout.GrandTotalRow) {
|
|
1035
|
+
const grandTotalRow = layout.GrandTotalRow === true || layout.GrandTotalRow === 'top'
|
|
1036
|
+
? 'top'
|
|
1037
|
+
: layout.GrandTotalRow === 'bottom'
|
|
1038
|
+
? 'bottom'
|
|
1039
|
+
: null;
|
|
1040
|
+
this.gridApi.setGridOption('grandTotalRow', grandTotalRow);
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
this.gridApi.setGridOption('grandTotalRow', null);
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* GrandTotalColumn
|
|
1047
|
+
*/
|
|
1048
|
+
if (layout.GrandTotalColumn) {
|
|
1049
|
+
const grandTotalColumn = layout.GrandTotalColumn === true || layout.GrandTotalColumn === 'before'
|
|
1050
|
+
? 'before'
|
|
1051
|
+
: layout.GrandTotalColumn === 'after'
|
|
1052
|
+
? 'after'
|
|
1053
|
+
: null;
|
|
1054
|
+
this.gridApi.setGridOption('pivotRowTotals', grandTotalColumn);
|
|
1055
|
+
}
|
|
1056
|
+
else {
|
|
1057
|
+
this.gridApi.setGridOption('pivotRowTotals', null);
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* PivotGroupTotalColumn
|
|
1061
|
+
*/
|
|
1062
|
+
if (layout.PivotGroupTotalColumn) {
|
|
1063
|
+
const pivotGroupTotalColumn = layout.PivotGroupTotalColumn === true || layout.PivotGroupTotalColumn === 'before'
|
|
1064
|
+
? 'before'
|
|
1065
|
+
: layout.PivotGroupTotalColumn === 'after'
|
|
1066
|
+
? 'after'
|
|
1067
|
+
: null;
|
|
1068
|
+
this.gridApi.setGridOption('pivotColumnGroupTotals', pivotGroupTotalColumn);
|
|
1069
|
+
}
|
|
1070
|
+
else {
|
|
1071
|
+
this.gridApi.setGridOption('pivotColumnGroupTotals', null);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1013
1074
|
applyPivotExpandLevel(layout) {
|
|
1014
1075
|
const PivotExpandLevel = layout.PivotExpandLevel ?? -1;
|
|
1015
1076
|
const allDisplayedColumnGroups = this.gridApi.getAllDisplayedColumnGroups();
|
|
@@ -1045,4 +1106,229 @@ export class LayoutManager extends LMEmitter {
|
|
|
1045
1106
|
}
|
|
1046
1107
|
return res;
|
|
1047
1108
|
}
|
|
1109
|
+
setupPivotTotals() {
|
|
1110
|
+
const _original_processPivotResultColDef = this.gridApi.getGridOption('processPivotResultColDef');
|
|
1111
|
+
this.gridApi.setGridOption('processPivotResultColDef', (resulColDef) => {
|
|
1112
|
+
_original_processPivotResultColDef?.(resulColDef);
|
|
1113
|
+
if (!isPivotLayoutModel(this.currentLayout)) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
// column that contain the aggregated total for each value column per row
|
|
1117
|
+
this.patchGrandTotalColumn(resulColDef);
|
|
1118
|
+
this.patchPivotGroupTotalColumn(resulColDef);
|
|
1119
|
+
});
|
|
1120
|
+
const _original_processPivotResultColGroupDef = this.gridApi.getGridOption('processPivotResultColGroupDef');
|
|
1121
|
+
this.gridApi.setGridOption('processPivotResultColGroupDef', (colGroupDef) => {
|
|
1122
|
+
_original_processPivotResultColGroupDef?.(colGroupDef);
|
|
1123
|
+
if (!isPivotLayoutModel(this.currentLayout)) {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
this.patchPivotTotalColumn(colGroupDef);
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
isPivotRowTotalColDef(colDef) {
|
|
1130
|
+
return colDef.colId?.startsWith('PivotRowTotal_');
|
|
1131
|
+
}
|
|
1132
|
+
patchGrandTotalColumn(resultColDef) {
|
|
1133
|
+
if (!isPivotLayoutModel(this.currentLayout) || !this.currentLayout.GrandTotalColumn) {
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (this.isPivotRowTotalColDef(resultColDef)) {
|
|
1137
|
+
resultColDef.headerName = `Grand Total ${resultColDef.headerName}`;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
isPivotGroupTotalColumn(colDef) {
|
|
1141
|
+
// pivot group total are spanning cross all aggregations
|
|
1142
|
+
// therefore the last part of the colId is empty (hence the "dangling" underscore)
|
|
1143
|
+
return colDef.colId?.startsWith('pivot_') && colDef.colId?.endsWith('_');
|
|
1144
|
+
}
|
|
1145
|
+
patchPivotGroupTotalColumn(resultColDef) {
|
|
1146
|
+
if (!isPivotLayoutModel(this.currentLayout) || !this.currentLayout.PivotGroupTotalColumn) {
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (this.isPivotGroupTotalColumn(resultColDef)) {
|
|
1150
|
+
// resultColDef
|
|
1151
|
+
const colInfo = this.destructurePivotColumnId(resultColDef, {
|
|
1152
|
+
pivotColIds: this.currentLayout.PivotColumns,
|
|
1153
|
+
aggColIds: this.currentLayout.PivotAggregationColumns.map((col) => col.ColumnId),
|
|
1154
|
+
});
|
|
1155
|
+
if (colInfo !== '!unknown!') {
|
|
1156
|
+
const currentPivotKey = colInfo.pivotKeys[colInfo.pivotKeys.length - 1];
|
|
1157
|
+
resultColDef.headerName = `${currentPivotKey} Total`;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
patchPivotTotalColumn(colGroupDef) {
|
|
1162
|
+
const hasPivotTotalCols = (pivotLayout) => {
|
|
1163
|
+
return pivotLayout.PivotAggregationColumns?.some((aggCol) => !!aggCol.TotalColumn);
|
|
1164
|
+
};
|
|
1165
|
+
const isPivotTotalColDef = (colDef) => {
|
|
1166
|
+
return !!colDef.pivotTotalColumnIds?.length;
|
|
1167
|
+
};
|
|
1168
|
+
if (!isPivotLayoutModel(this.currentLayout) || !hasPivotTotalCols(this.currentLayout)) {
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
const pivotRowTotalColDefsBefore = [];
|
|
1172
|
+
const pivotRowTotalColDefsAfter = [];
|
|
1173
|
+
const pivotTotalColDefsBefore = [];
|
|
1174
|
+
const pivotTotalColDefsAfter = [];
|
|
1175
|
+
const normalColDefs = [];
|
|
1176
|
+
colGroupDef.children.forEach((colDef) => {
|
|
1177
|
+
if (this.isPivotRowTotalColDef(colDef)) {
|
|
1178
|
+
if (this.gridApi.getGridOption('pivotRowTotals') === 'after') {
|
|
1179
|
+
pivotRowTotalColDefsAfter.push(colDef);
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
pivotRowTotalColDefsBefore.push(colDef);
|
|
1183
|
+
}
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
if (isPivotTotalColDef(colDef)) {
|
|
1187
|
+
if (!colDef.colId.startsWith('pivot_')) {
|
|
1188
|
+
this.warn(`Pivot total column ${colDef.colId} is not prefixed with 'pivot_', skipping...`);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
// we do this for all total cols, but we will hide the ones that are not visible
|
|
1192
|
+
colDef.columnGroupShow = undefined;
|
|
1193
|
+
colDef.headerName = `Total ${colDef.headerName}`;
|
|
1194
|
+
const totalColConfig = this.getPivotTotalColumnConfig(colDef, this.currentLayout);
|
|
1195
|
+
if (!totalColConfig.visible) {
|
|
1196
|
+
colDef.hide = true;
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
if (totalColConfig.position === 'after') {
|
|
1200
|
+
pivotTotalColDefsAfter.push(colDef);
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
pivotTotalColDefsBefore.push(colDef);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
normalColDefs.push(colDef);
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
colGroupDef.children = [
|
|
1212
|
+
...pivotRowTotalColDefsBefore,
|
|
1213
|
+
...pivotTotalColDefsBefore,
|
|
1214
|
+
...normalColDefs,
|
|
1215
|
+
...pivotTotalColDefsAfter,
|
|
1216
|
+
...pivotRowTotalColDefsAfter,
|
|
1217
|
+
];
|
|
1218
|
+
}
|
|
1219
|
+
// supports only pivotTotalColumns (aggregation total) and pivotGroupTotal
|
|
1220
|
+
// AG Grid builds the ID as: `pivot_${pivotCols.join('-')}_${pivotKeys.join('-')}_${measureColumnId}`
|
|
1221
|
+
// see https://github.com/ag-grid/ag-grid/blob/e0cfe533b55b75cdc148cdfb1a4e977731dc0712/packages/ag-grid-enterprise/src/pivot/pivotColDefService.ts#L454C16-L454C88
|
|
1222
|
+
destructurePivotColumnId(colDef, currentModel) {
|
|
1223
|
+
const { colId } = colDef;
|
|
1224
|
+
// Basic validation
|
|
1225
|
+
if (!colId?.startsWith('pivot_')) {
|
|
1226
|
+
this.warn(`Column id must start with 'pivot_': ${colId}`);
|
|
1227
|
+
return '!unknown!';
|
|
1228
|
+
}
|
|
1229
|
+
const hasSpecialChars = (arr) => arr.some((str) => str.includes('-') || str.includes('_'));
|
|
1230
|
+
// AG Grid uses _ and - as delimiters
|
|
1231
|
+
// if we have these special characters in the column IDs, we need to parse them differently
|
|
1232
|
+
const colIdsOrKeysContainSpecialChars = hasSpecialChars(currentModel.pivotColIds) ||
|
|
1233
|
+
hasSpecialChars(currentModel.aggColIds) ||
|
|
1234
|
+
hasSpecialChars(colDef.pivotKeys || []);
|
|
1235
|
+
// Fast path - no special characters
|
|
1236
|
+
if (!colIdsOrKeysContainSpecialChars) {
|
|
1237
|
+
// Split by underscore to get 4 parts
|
|
1238
|
+
const parts = colId.split('_');
|
|
1239
|
+
if (parts.length !== 4) {
|
|
1240
|
+
this.warn(`Unsupported format of pivot total column id: ${colId}`);
|
|
1241
|
+
return '!unknown!';
|
|
1242
|
+
}
|
|
1243
|
+
const [_pivotPrefix, pivotColsTxt, pivotKeysTxt, aggregationColumnId] = parts;
|
|
1244
|
+
const pivotColumnIds = pivotColsTxt.split('-');
|
|
1245
|
+
const pivotKeys = pivotKeysTxt.split('-');
|
|
1246
|
+
const pivotColumnId = pivotColumnIds[pivotKeys.length - 1];
|
|
1247
|
+
return {
|
|
1248
|
+
pivotColumnIds,
|
|
1249
|
+
pivotKeys,
|
|
1250
|
+
pivotColumnId,
|
|
1251
|
+
aggregationColumnId: aggregationColumnId !== '' ? aggregationColumnId : undefined,
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
// the complex path, where we have to handle the special characters
|
|
1255
|
+
// Remove 'pivot_' prefix
|
|
1256
|
+
const withoutPrefix = colId.slice(6);
|
|
1257
|
+
// Check if it's a pivot group total
|
|
1258
|
+
const isPivotGroupTotal = withoutPrefix.endsWith('_');
|
|
1259
|
+
// Get pivot keys from colDef (more reliable than string parsing)
|
|
1260
|
+
const pivotKeys = colDef.pivotKeys || [];
|
|
1261
|
+
if (isPivotGroupTotal) {
|
|
1262
|
+
// Remove trailing underscore for pivot group totals
|
|
1263
|
+
const content = withoutPrefix.slice(0, -1);
|
|
1264
|
+
// The remaining content should be the pivot columns
|
|
1265
|
+
const pivotColumnIds = currentModel.pivotColIds.filter((id) => content.includes(id));
|
|
1266
|
+
if (!pivotColumnIds.length) {
|
|
1267
|
+
this.warn(`Could not identify pivot columns in: ${content}`);
|
|
1268
|
+
return '!unknown!';
|
|
1269
|
+
}
|
|
1270
|
+
return {
|
|
1271
|
+
pivotColumnIds,
|
|
1272
|
+
pivotKeys,
|
|
1273
|
+
pivotColumnId: pivotColumnIds[pivotKeys.length],
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
// For regular pivot columns, work backwards to find aggregation column
|
|
1277
|
+
const parts = withoutPrefix.split('_');
|
|
1278
|
+
const lastPart = parts[parts.length - 1];
|
|
1279
|
+
// Find the longest matching aggregation column id from the end
|
|
1280
|
+
const aggregationColumnId = currentModel.aggColIds.find((aggId) => lastPart.endsWith(aggId));
|
|
1281
|
+
if (!aggregationColumnId) {
|
|
1282
|
+
this.warn(`Could not identify aggregation column in: ${lastPart}`);
|
|
1283
|
+
return '!unknown!';
|
|
1284
|
+
}
|
|
1285
|
+
// Remove aggregation part and get pivot columns
|
|
1286
|
+
const withoutAgg = withoutPrefix.slice(0, -(aggregationColumnId.length + 1));
|
|
1287
|
+
const pivotColumnIds = currentModel.pivotColIds.filter((id) => withoutAgg.includes(id));
|
|
1288
|
+
if (!pivotColumnIds.length) {
|
|
1289
|
+
this.warn(`Could not identify pivot columns in: ${withoutAgg}`);
|
|
1290
|
+
return '!unknown!';
|
|
1291
|
+
}
|
|
1292
|
+
return {
|
|
1293
|
+
pivotColumnIds,
|
|
1294
|
+
pivotKeys,
|
|
1295
|
+
pivotColumnId: pivotColumnIds[pivotKeys.length - 1],
|
|
1296
|
+
aggregationColumnId,
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
getPivotTotalColumnConfig(colDef, currentPivotLayout) {
|
|
1300
|
+
const defaultHiddenConfig = {
|
|
1301
|
+
visible: false,
|
|
1302
|
+
};
|
|
1303
|
+
const colIdInfo = this.destructurePivotColumnId(colDef, {
|
|
1304
|
+
pivotColIds: this.currentLayout.PivotColumns,
|
|
1305
|
+
aggColIds: this.currentLayout.PivotAggregationColumns.map((col) => col.ColumnId),
|
|
1306
|
+
});
|
|
1307
|
+
if (colIdInfo === '!unknown!') {
|
|
1308
|
+
return defaultHiddenConfig;
|
|
1309
|
+
}
|
|
1310
|
+
const { pivotColumnId, aggregationColumnId } = colIdInfo;
|
|
1311
|
+
const layoutAggCol = currentPivotLayout.PivotAggregationColumns?.find((col) => col.ColumnId === aggregationColumnId);
|
|
1312
|
+
if (!layoutAggCol) {
|
|
1313
|
+
this.warn(`Pivot Totals: could NOT find aggregation(value) column with id ${aggregationColumnId}`);
|
|
1314
|
+
return defaultHiddenConfig;
|
|
1315
|
+
}
|
|
1316
|
+
const layoutPivotTotalColumn = layoutAggCol.TotalColumn;
|
|
1317
|
+
if (!layoutPivotTotalColumn) {
|
|
1318
|
+
return defaultHiddenConfig;
|
|
1319
|
+
}
|
|
1320
|
+
if (Array.isArray(layoutPivotTotalColumn)) {
|
|
1321
|
+
const pivotSpecificConfig = layoutPivotTotalColumn.find((config) => config.PivotColumnId === pivotColumnId);
|
|
1322
|
+
return {
|
|
1323
|
+
visible: pivotSpecificConfig && pivotSpecificConfig.ShowTotal !== false,
|
|
1324
|
+
position: pivotSpecificConfig?.ShowTotal === 'after' ? 'after' : 'before',
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
return {
|
|
1329
|
+
visible: !!layoutPivotTotalColumn,
|
|
1330
|
+
position: layoutPivotTotalColumn === 'after' ? 'after' : 'before',
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1048
1334
|
}
|
|
@@ -139,6 +139,9 @@ export function normalizePivotLayoutModel(layout) {
|
|
|
139
139
|
// make it an own property
|
|
140
140
|
layout.SuppressAggFuncInHeader = undefined;
|
|
141
141
|
}
|
|
142
|
+
if (!layout.GrandTotalRow) {
|
|
143
|
+
layout.GrandTotalRow = false;
|
|
144
|
+
}
|
|
142
145
|
return layout;
|
|
143
146
|
}
|
|
144
147
|
export function normalizeLayoutModel(layout, options) {
|