devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
475 lines (466 loc) • 18 kB
JavaScript
/**
* DevExtreme (esm/__internal/grids/pivot_grid/summary_display_modes/module.js)
* Version: 22.1.9
* Build date: Tue Apr 18 2023
*
* Copyright (c) 2012 - 2023 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
isFunction,
isDefined,
isObject
} from "../../../../core/utils/type";
import {
extend
} from "../../../../core/utils/extend";
import pivotGridUtils, {
findField,
foreachTree
} from "../module_widget_utils";
const COLUMN = "column";
const ROW = "row";
const NULL = null;
const calculatePercentValue = function(value, totalValue) {
let result = value / totalValue;
if (!isDefined(value) || isNaN(result)) {
result = null
}
return result
};
const percentOfGrandTotal = function(e, dimension) {
return calculatePercentValue(e.value(), e.grandTotal(dimension).value())
};
const percentOfParent = function(e, dimension) {
const parent = e.parent(dimension);
const parentValue = parent ? parent.value() : e.value();
return calculatePercentValue(e.value(), parentValue)
};
const createAbsoluteVariationExp = function(allowCrossGroup) {
return function(e) {
const prevCell = e.prev(COLUMN, allowCrossGroup);
const prevValue = prevCell && prevCell.value();
if (isDefined(prevValue) && isDefined(e.value())) {
return e.value() - prevValue
}
return null
}
};
const createPercentVariationExp = function(allowCrossGroup) {
const absoluteExp = createAbsoluteVariationExp(allowCrossGroup);
return function(e) {
const absVar = absoluteExp(e);
const prevCell = e.prev(COLUMN, allowCrossGroup);
const prevValue = prevCell && prevCell.value();
return null !== absVar && prevValue ? absVar / prevValue : null
}
};
const summaryDictionary = {
percentOfColumnTotal: e => percentOfParent(e, ROW),
percentOfRowTotal: e => percentOfParent(e, COLUMN),
percentOfColumnGrandTotal: e => percentOfGrandTotal(e, ROW),
percentOfRowGrandTotal: e => percentOfGrandTotal(e, COLUMN),
percentOfGrandTotal: e => percentOfGrandTotal(e)
};
const getPrevCellCrossGroup = function(cell, direction) {
if (!cell || !cell.parent(direction)) {
return
}
let prevCell = cell.prev(direction);
if (!prevCell) {
prevCell = getPrevCellCrossGroup(cell.parent(direction), direction)
}
return prevCell
};
const createRunningTotalExpr = function(field) {
if (!field.runningTotal) {
return
}
const direction = field.runningTotal === COLUMN ? ROW : COLUMN;
return function(e) {
const prevCell = field.allowCrossGroupCalculation ? getPrevCellCrossGroup(e, direction) : e.prev(direction, false);
let value = e.value(true);
const prevValue = prevCell && prevCell.value(true);
if (isDefined(prevValue) && isDefined(value)) {
value = prevValue + value
} else if (isDefined(prevValue)) {
value = prevValue
}
return value
}
};
function createCache() {
return {
fields: {},
positions: {}
}
}
function getFieldPos(descriptions, field, cache) {
let fieldParams = {
index: -1
};
if (!isObject(field)) {
if (cache.fields[field]) {
field = cache[field]
} else {
const allFields = descriptions.columns.concat(descriptions.rows).concat(descriptions.values);
const fieldIndex = findField(allFields, field);
field = cache[field] = allFields[fieldIndex]
}
}
if (field) {
const area = field.area || "data";
fieldParams = cache.positions[field.index] = cache.positions[field.index] || {
area: area,
index: descriptions["data" === area ? "values" : area + "s"].indexOf(field)
}
}
return fieldParams
}
function getPathFieldName(dimension) {
return dimension === ROW ? "_rowPath" : "_columnPath"
}
const SummaryCell = function(columnPath, rowPath, data, descriptions, fieldIndex, fieldsCache) {
this._columnPath = columnPath;
this._rowPath = rowPath;
this._fieldIndex = fieldIndex;
this._fieldsCache = fieldsCache || createCache();
this._data = data;
this._descriptions = descriptions;
const cell = data.values && data.values[rowPath[0].index] && data.values[rowPath[0].index][columnPath[0].index];
if (cell) {
cell.originalCell = cell.originalCell || cell.slice();
cell.postProcessedFlags = cell.postProcessedFlags || [];
this._cell = cell
}
};
SummaryCell.prototype = extend(SummaryCell.prototype, {
_getPath(dimension) {
return this[getPathFieldName(dimension)]
},
_getDimension(dimension) {
dimension = dimension === ROW ? "rows" : "columns";
return this._descriptions[dimension]
},
_createCell(config) {
return new SummaryCell(config._columnPath || this._columnPath, config._rowPath || this._rowPath, this._data, this._descriptions, this._fieldIndex)
},
parent(direction) {
const path = this._getPath(direction).slice();
const config = {};
path.shift();
if (path.length) {
config[getPathFieldName(direction)] = path;
return this._createCell(config)
}
return null
},
children(direction) {
const path = this._getPath(direction).slice();
const item = path[0];
const result = [];
const cellConfig = {};
if (item.children) {
for (let i = 0; i < item.children.length; i += 1) {
cellConfig[getPathFieldName(direction)] = [item.children[i]].concat(path.slice());
result.push(this._createCell(cellConfig))
}
}
return result
},
grandTotal(direction) {
const config = {};
const rowPath = this._rowPath;
const columnPath = this._columnPath;
const dimensionPath = this._getPath(direction);
const pathFieldName = getPathFieldName(direction);
if (!direction) {
config._rowPath = [rowPath[rowPath.length - 1]];
config._columnPath = [columnPath[columnPath.length - 1]]
} else {
config[pathFieldName] = [dimensionPath[dimensionPath.length - 1]]
}
return this._createCell(config)
},
next(direction, allowCrossGroup) {
const currentPath = this._getPath(direction);
const item = currentPath[0];
let parent = this.parent(direction);
let siblings;
if (parent) {
const index = currentPath[1].children.indexOf(item);
siblings = parent.children(direction);
if (siblings[index + 1]) {
return siblings[index + 1]
}
}
if (allowCrossGroup && parent) {
do {
parent = parent.next(direction, allowCrossGroup);
siblings = parent ? parent.children(direction) : []
} while (parent && !siblings.length);
return siblings[0] || null
}
return null
},
prev(direction, allowCrossGroup) {
const currentPath = this._getPath(direction);
const item = currentPath[0];
let parent = this.parent(direction);
let siblings;
if (parent) {
const index = currentPath[1].children.indexOf(item);
siblings = parent.children(direction);
if (siblings[index - 1]) {
return siblings[index - 1]
}
}
if (allowCrossGroup && parent) {
do {
parent = parent.prev(direction, allowCrossGroup);
siblings = parent ? parent.children(direction) : []
} while (parent && !siblings.length);
return siblings[siblings.length - 1] || null
}
return null
},
cell() {
return this._cell
},
field(area) {
if ("data" === area) {
return this._descriptions.values[this._fieldIndex]
}
const path = this._getPath(area);
const descriptions = this._getDimension(area);
const field = descriptions[path.length - 2];
return field || null
},
child(direction, fieldValue) {
let childLevelField;
const children = this.children(direction);
for (let i = 0; i < children.length; i += 1) {
childLevelField = childLevelField || children[i].field(direction);
if (children[i].value(childLevelField) === fieldValue) {
return children[i]
}
}
return null
},
slice(field, value) {
const that = this;
const config = {};
const fieldPos = getFieldPos(this._descriptions, field, this._fieldsCache);
const {
area: area
} = fieldPos;
const fieldIndex = fieldPos.index;
let sliceCell = null;
const newPath = [];
if (area === ROW || area === COLUMN) {
const path = this._getPath(area).slice();
const level = -1 !== fieldIndex && path.length - 2 - fieldIndex;
if (path[level]) {
newPath[path.length - 1] = path[path.length - 1];
for (let i = level; i >= 0; i -= 1) {
if (path[i + 1]) {
const childItems = path[i + 1].children || [];
const currentValue = i === level ? value : path[i].value;
path[i] = void 0;
for (let childIndex = 0; childIndex < childItems.length; childIndex += 1) {
if (childItems[childIndex].value === currentValue) {
path[i] = childItems[childIndex];
break
}
}
}
if (void 0 === path[i]) {
return sliceCell
}
}
config[getPathFieldName(area)] = path;
sliceCell = that._createCell(config)
}
}
return sliceCell
},
value(arg1, arg2) {
const cell = this._cell;
let fieldIndex = this._fieldIndex;
const fistArgIsBoolean = true === arg1 || false === arg1;
const field = !fistArgIsBoolean ? arg1 : null;
const needCalculatedValue = fistArgIsBoolean && arg1 || arg2;
if (isDefined(field)) {
const fieldPos = getFieldPos(this._descriptions, field, this._fieldsCache);
fieldIndex = fieldPos.index;
if ("data" !== fieldPos.area) {
const path = this._getPath(fieldPos.area);
const level = -1 !== fieldIndex && path.length - 2 - fieldIndex;
return path[level] && path[level].value
}
}
if (cell && cell.originalCell) {
return needCalculatedValue ? cell[fieldIndex] : cell.originalCell[fieldIndex]
}
return null
},
isPostProcessed(field) {
let fieldIndex = this._fieldIndex;
if (isDefined(field)) {
const fieldPos = getFieldPos(this._descriptions, field, this._fieldsCache);
fieldIndex = fieldPos.index;
if ("data" !== fieldPos.area) {
return false
}
}
return !!(this._cell && this._cell.postProcessedFlags[fieldIndex])
}
});
function getExpression(field) {
const {
summaryDisplayMode: summaryDisplayMode
} = field;
const crossGroupCalculation = field.allowCrossGroupCalculation;
let expression = null;
if (isFunction(field.calculateSummaryValue)) {
expression = field.calculateSummaryValue
} else if (summaryDisplayMode) {
if ("absoluteVariation" === summaryDisplayMode) {
expression = createAbsoluteVariationExp(crossGroupCalculation)
} else if ("percentVariation" === summaryDisplayMode) {
expression = createPercentVariationExp(crossGroupCalculation)
} else {
expression = summaryDictionary[summaryDisplayMode]
}
if (expression && !field.format && -1 !== summaryDisplayMode.indexOf("percent")) {
pivotGridUtils.setFieldProperty(field, "format", "percent")
}
}
return expression
}
function processDataCell(data, rowIndex, columnIndex, isRunningTotalCalculation) {
const values = data.values[rowIndex][columnIndex] = data.values[rowIndex][columnIndex] || [];
const {
originalCell: originalCell
} = values;
if (!originalCell) {
return
}
if (values.allowResetting || !isRunningTotalCalculation) {
data.values[rowIndex][columnIndex] = originalCell.slice()
}
data.values[rowIndex][columnIndex].allowResetting = isRunningTotalCalculation
}
function applyDisplaySummaryMode(descriptions, data) {
const expressions = [];
const columnElements = [{
index: data.grandTotalColumnIndex,
children: data.columns
}];
const rowElements = [{
index: data.grandTotalRowIndex,
children: data.rows
}];
const valueFields = descriptions.values;
const fieldsCache = createCache();
data.values = data.values || [];
foreachTree(columnElements, columnPath => {
columnPath[0].isEmpty = []
}, false);
foreachTree(rowElements, rowPath => {
const rowItem = rowPath[0];
rowItem.isEmpty = [];
data.values[rowItem.index] = data.values[rowItem.index] || [];
foreachTree(columnElements, columnPath => {
const columnItem = columnPath[0];
let isEmptyCell;
processDataCell(data, rowItem.index, columnItem.index, false);
for (let i = 0; i < valueFields.length; i += 1) {
const field = valueFields[i];
const expression = expressions[i] = void 0 === expressions[i] ? getExpression(field) : expressions[i];
isEmptyCell = false;
if (expression) {
const expressionArg = new SummaryCell(columnPath, rowPath, data, descriptions, i, fieldsCache);
const cell = expressionArg.cell();
const value = cell[i] = expression(expressionArg);
cell.postProcessedFlags[i] = true;
isEmptyCell = null === value || void 0 === value
}
if (void 0 === columnItem.isEmpty[i]) {
columnItem.isEmpty[i] = true
}
if (void 0 === rowItem.isEmpty[i]) {
rowItem.isEmpty[i] = true
}
if (!isEmptyCell) {
rowItem.isEmpty[i] = columnItem.isEmpty[i] = false
}
}
}, false)
}, false);
data.isEmptyGrandTotalRow = rowElements[0].isEmpty;
data.isEmptyGrandTotalColumn = columnElements[0].isEmpty
}
function applyRunningTotal(descriptions, data) {
const expressions = [];
const columnElements = [{
index: data.grandTotalColumnIndex,
children: data.columns
}];
const rowElements = [{
index: data.grandTotalRowIndex,
children: data.rows
}];
const valueFields = descriptions.values;
const fieldsCache = createCache();
data.values = data.values || [];
foreachTree(rowElements, rowPath => {
const rowItem = rowPath[0];
data.values[rowItem.index] = data.values[rowItem.index] || [];
foreachTree(columnElements, columnPath => {
const columnItem = columnPath[0];
processDataCell(data, rowItem.index, columnItem.index, true);
for (let i = 0; i < valueFields.length; i += 1) {
const field = valueFields[i];
const expression = expressions[i] = void 0 === expressions[i] ? createRunningTotalExpr(field) : expressions[i];
if (expression) {
const expressionArg = new SummaryCell(columnPath, rowPath, data, descriptions, i, fieldsCache);
const cell = expressionArg.cell();
cell[i] = expression(expressionArg);
cell.postProcessedFlags[i] = true
}
}
}, false)
}, false)
}
function createMockSummaryCell(descriptions, fields, indices) {
const summaryCell = new SummaryCell([], [], {}, descriptions, 0);
summaryCell.value = function(fieldId) {
if (isDefined(fieldId)) {
const index = findField(fields, fieldId);
const field = fields[index];
if (!indices[index] && field && !isDefined(field.area)) {
descriptions.values.push(field);
indices[index] = true
}
}
};
summaryCell.grandTotal = function() {
return this
};
summaryCell.children = function() {
return []
};
return summaryCell
}
export default {
Cell: SummaryCell,
summaryDictionary: summaryDictionary,
getExpression: getExpression,
applyRunningTotal: applyRunningTotal,
createMockSummaryCell: createMockSummaryCell,
applyDisplaySummaryMode: applyDisplaySummaryMode
};
export {
SummaryCell as Cell, summaryDictionary, getExpression, applyRunningTotal, createMockSummaryCell, applyDisplaySummaryMode
};