@antv/s2
Version:
effective spreadsheet render core lib
450 lines • 20.7 kB
JavaScript
import { compact, each, every, filter, find, first, flatMap, forEach, get, has, includes, indexOf, isArray, isEmpty, isNumber, map, omit, some, uniq, unset, } from 'lodash';
import { Aggregation, MULTI_VALUE, QueryDataType, } from '../common';
import { EXTRA_FIELD, TOTAL_VALUE, VALUE_FIELD } from '../common/constant';
import { DEBUG_TRANSFORM_DATA, DebuggerUtil } from '../common/debug';
import { i18n } from '../common/i18n';
import { Node } from '../facet/layout/node';
import { resolveNillString } from '../utils';
import { getAggregationAndCalcFuncByQuery } from '../utils/data-set-operate';
import { deleteMetaById, filterExtraDimension, flattenIndexesData, getDataPath, getDataPathPrefix, getExistValues, getFlattenDimensionValues, getIndexFields, getSatisfiedPivotMetaValues, isMultiValue, transformDimensionsValues, transformIndexesData, } from '../utils/dataset/pivot-data-set';
import { calcActionByType } from '../utils/number-calculate';
import { getSortedPivotMeta, handleSortAction } from '../utils/sort-action';
import { BaseDataSet } from './base-data-set';
import { CellData } from './cell-data';
export class PivotDataSet extends BaseDataSet {
constructor() {
super(...arguments);
/**
* 排序优先级:
* 1、sortParams里的条件优先级高于原始数据
* 2、sortParams多个item:按照顺序优先级,排在后面的优先级高
* 3、item中多个条件:sortByField > sortFunc > sortBy > sortMethod
*/
this.handleDimensionValuesSort = () => {
each(this.sortParams, (item) => {
const { sortFieldId, sortByMeasure } = item;
// 万物排序的前提
if (!sortFieldId) {
return;
}
const originValues = [...(this.sortedDimensionValues[sortFieldId] || [])];
const result = handleSortAction({
dataSet: this,
sortParam: item,
originValues,
isSortByMeasure: !isEmpty(sortByMeasure),
});
this.sortedDimensionValues[sortFieldId] = result;
this.handlePivotMetaSort(sortFieldId, result);
});
};
this.getTotalStatus = (query) => {
const { columns, rows } = this.fields;
const isTotals = (dimensions, isSubTotal) => {
if (isSubTotal) {
const firstDimension = find(dimensions, (item) => !has(query, item));
return !!(firstDimension && firstDimension !== first(dimensions));
}
return every(dimensions, (item) => !has(query, item));
};
return {
isRowGrandTotal: isTotals(filterExtraDimension(rows)),
isRowSubTotal: isTotals(rows, true),
isColGrandTotal: isTotals(filterExtraDimension(columns)),
isColSubTotal: isTotals(columns, true),
};
};
}
getExistValuesByDataItem(data, values) {
return getExistValues(data, values);
}
/**
* When data related config changed, we need
* 1、re-process config
* 2、re-transform origin data
* 3、sort and other things
* @param dataCfg
*/
setDataCfg(dataCfg) {
super.setDataCfg(dataCfg);
const { rows } = this.fields;
this.sortedDimensionValues = {};
this.rowPivotMeta = new Map();
this.colPivotMeta = new Map();
this.dimensionValuesCache = new Map();
this.transformIndexesData(this.originData, rows);
this.handleDimensionValuesSort();
}
transformIndexesData(data, rows) {
const { columns, values, valueInCols } = this.fields;
let result;
DebuggerUtil.getInstance().debugCallback(DEBUG_TRANSFORM_DATA, () => {
result = transformIndexesData({
rows: getIndexFields(rows),
columns: getIndexFields(columns),
values: values,
valueInCols: valueInCols,
data,
indexesData: this.indexesData,
sortedDimensionValues: this.sortedDimensionValues,
rowPivotMeta: this.rowPivotMeta,
colPivotMeta: this.colPivotMeta,
getExistValuesByDataItem: this.getExistValuesByDataItem,
});
this.indexesData = result.indexesData;
this.rowPivotMeta = result.rowPivotMeta;
this.colPivotMeta = result.colPivotMeta;
this.sortedDimensionValues = result.sortedDimensionValues;
});
return result;
}
/**
* Provide a way to append some drill-down data in indexesData
* @param extraRowField
* @param drillDownData
* @param rowNode
*/
transformDrillDownData(extraRowField, drillDownData, rowNode) {
var _a;
const currentRowFields = Node.getFieldPath(rowNode, true);
const nextRowFields = [...currentRowFields, extraRowField];
const store = this.spreadsheet.store;
// 2. 检查该节点是否已经存在下钻维度
const rowNodeId = rowNode === null || rowNode === void 0 ? void 0 : rowNode.id;
const idPathMap = (_a = store.get('drillDownIdPathMap')) !== null && _a !== void 0 ? _a : new Map();
if (idPathMap.has(rowNodeId)) {
// the current node has a drill-down field, clean it
forEach(idPathMap.get(rowNodeId), (path) => {
unset(this.indexesData, path);
});
deleteMetaById(this.rowPivotMeta, rowNodeId);
}
// 3、转换数据
const { paths: drillDownDataPaths } = this.transformIndexesData(drillDownData, nextRowFields);
/*
* 4、record data paths by nodeId
* set new drill-down data path
*/
idPathMap.set(rowNodeId, drillDownDataPaths);
store.set('drillDownIdPathMap', idPathMap);
}
/**
* Clear drill down data by rowNodeId
* rowNodeId is undefined => clear all
* @param rowNodeId
*/
clearDrillDownData(rowNodeId) {
const store = this.spreadsheet.store;
const idPathMap = store.get('drillDownIdPathMap');
if (!idPathMap) {
return false;
}
const drillDownDataCache = store.get('drillDownDataCache', []);
if (rowNodeId) {
// 1. 删除 indexesData 当前下钻层级对应数据
const currentIdPathMap = idPathMap.get(rowNodeId);
if (currentIdPathMap) {
forEach(currentIdPathMap, (path) => {
unset(this.indexesData, path);
});
}
// 2. 删除 rowPivotMeta 当前下钻层级对应 meta 信息
deleteMetaById(this.rowPivotMeta, rowNodeId);
// 3. 删除下钻缓存路径
idPathMap.delete(rowNodeId);
// 4. 过滤清除的下钻缓存
const restDataCache = filter(drillDownDataCache, (cache) => idPathMap.has(cache === null || cache === void 0 ? void 0 : cache.rowId));
store.set('drillDownDataCache', restDataCache);
// 5. 过滤清除的下钻层级
const restDrillLevels = restDataCache.map((cache) => cache === null || cache === void 0 ? void 0 : cache.drillLevel);
const drillDownFieldInLevel = store.get('drillDownFieldInLevel', []);
const restFieldInLevel = drillDownFieldInLevel.filter((filed) => includes(restDrillLevels, filed === null || filed === void 0 ? void 0 : filed.drillLevel));
store.set('drillDownFieldInLevel', restFieldInLevel);
}
else {
idPathMap.clear();
/*
* 需要对应清空所有下钻后的dataCfg信息
* 因此如果缓存有下钻前原始dataCfg,需要清空所有的下钻数据
*/
const originalDataCfg = this.spreadsheet.store.get('originalDataCfg');
if (!isEmpty(originalDataCfg)) {
this.spreadsheet.setDataCfg(originalDataCfg);
}
// 清空所有的下钻信息
this.spreadsheet.store.set('drillItemsNum', -1);
this.spreadsheet.store.set('drillDownDataCache', []);
this.spreadsheet.store.set('drillDownFieldInLevel', []);
}
store.set('drillDownIdPathMap', idPathMap);
return true;
}
handlePivotMetaSort(sortFieldId, sortedDimensionValues) {
const { rows, columns } = this.fields;
if (includes(rows, sortFieldId)) {
this.rowPivotMeta = getSortedPivotMeta({
pivotMeta: this.rowPivotMeta,
dimensions: rows,
sortFieldId,
sortedDimensionValues,
});
}
else if (includes(columns, sortFieldId)) {
this.colPivotMeta = getSortedPivotMeta({
pivotMeta: this.colPivotMeta,
dimensions: columns,
sortFieldId,
sortedDimensionValues,
});
}
}
processDataCfg(dataCfg) {
const { data, meta = [], fields, sortParams = [] } = dataCfg;
const { columns = [], rows = [], values, valueInCols, customValueOrder, } = fields;
let newColumns = columns;
let newRows = rows;
if (valueInCols) {
newColumns = this.isCustomMeasuresPosition(customValueOrder)
? this.handleCustomMeasuresOrder(customValueOrder, newColumns)
: uniq([...columns, EXTRA_FIELD]);
}
else {
newRows = this.isCustomMeasuresPosition(customValueOrder)
? this.handleCustomMeasuresOrder(customValueOrder, newRows)
: uniq([...rows, EXTRA_FIELD]);
}
const newMeta = this.processMeta(meta, i18n('数值'));
return {
data,
meta: newMeta,
fields: Object.assign(Object.assign({}, fields), { rows: newRows, columns: newColumns, values }),
sortParams,
};
}
getFieldsAndPivotMetaByField(field) {
const { rows = [], columns = [] } = this.fields || {};
if (rows.includes(field)) {
return {
dimensions: getIndexFields(rows),
pivotMeta: this.rowPivotMeta,
};
}
if (columns.includes(field)) {
return {
dimensions: getIndexFields(columns),
pivotMeta: this.colPivotMeta,
};
}
return {};
}
getDimensionValues(field, query = {}) {
var _a;
const { pivotMeta, dimensions } = this.getFieldsAndPivotMetaByField(field);
if (!pivotMeta || !dimensions) {
return [];
}
const isGetAllDimensionValues = isEmpty(query);
// 暂时先对获取某一个维度所有的 labels 这样的场景做缓存处理,因为内部 flatten 逻辑比较耗时
if (this.dimensionValuesCache.has(field) && isGetAllDimensionValues) {
return (_a = this.dimensionValuesCache.get(field)) !== null && _a !== void 0 ? _a : [];
}
const dimensionValues = transformDimensionsValues(query, dimensions, MULTI_VALUE);
const metaValues = getSatisfiedPivotMetaValues({
pivotMeta,
dimensionValues,
fields: dimensions,
fieldIdx: indexOf(dimensions, field),
queryType: QueryDataType.DetailOnly,
sortedDimensionValues: this.sortedDimensionValues,
});
const result = uniq(metaValues.map((meta) => resolveNillString(meta.value)));
if (isGetAllDimensionValues) {
this.dimensionValuesCache.set(field, result);
}
return result;
}
getTotalValue(query, totalStatus) {
const { options } = this.spreadsheet;
const effectiveStatus = some(totalStatus);
const status = effectiveStatus ? totalStatus : this.getTotalStatus(query);
const { aggregation, calcFunc } = getAggregationAndCalcFuncByQuery(status, options === null || options === void 0 ? void 0 : options.totals) || {};
// 聚合方式从用户配置的 s2Options.totals 取, 在触发前端兜底计算汇总逻辑时, 如果没有汇总的配置, 默认按 [求和] 计算,避免排序失效.
const defaultAggregation = isEmpty(options === null || options === void 0 ? void 0 : options.totals) && !this.spreadsheet.isHierarchyTreeType()
? Aggregation.SUM
: '';
const calcAction = calcActionByType[aggregation || defaultAggregation];
// 前端计算汇总值
if (calcAction || calcFunc) {
const data = this.getCellMultiData({
query,
queryType: QueryDataType.DetailOnly,
});
let totalValue = null;
if (calcFunc) {
totalValue = calcFunc(query, data, this.spreadsheet);
}
else if (calcAction) {
totalValue = calcAction(data, VALUE_FIELD);
}
return CellData.getCellData(Object.assign(Object.assign({}, omit(query, [EXTRA_FIELD])), { [query[EXTRA_FIELD]]: totalValue }), query[EXTRA_FIELD]);
}
}
getCellData(params) {
var _a, _b, _c;
const { query = {}, rowNode, isTotals = false, totalStatus } = params || {};
const { rows: originRows, columns } = this.fields;
let rows = originRows;
const drillDownIdPathMap = (_a = this.spreadsheet) === null || _a === void 0 ? void 0 : _a.store.get('drillDownIdPathMap');
/*
* 判断当前是否为下钻节点
* 需检查 rowNode.id 是否属于下钻根节点(drillDownIdPathMap.keys)的下属节点
*/
const isDrillDown = Array.from((_b = drillDownIdPathMap === null || drillDownIdPathMap === void 0 ? void 0 : drillDownIdPathMap.keys()) !== null && _b !== void 0 ? _b : []).some((parentPath) => rowNode === null || rowNode === void 0 ? void 0 : rowNode.id.startsWith(parentPath));
// 如果是下钻结点,行维度在 originRows 中并不存在
if (rowNode && isDrillDown) {
rows = (_c = Node.getFieldPath(rowNode, isDrillDown)) !== null && _c !== void 0 ? _c : originRows;
}
const indexRows = getIndexFields(rows);
const indexColumns = getIndexFields(columns);
const rowDimensionValues = transformDimensionsValues(query, indexRows);
const colDimensionValues = transformDimensionsValues(query, indexColumns);
const path = getDataPath({
rowDimensionValues,
colDimensionValues,
rowPivotMeta: this.rowPivotMeta,
colPivotMeta: this.colPivotMeta,
rowFields: indexRows,
colFields: indexColumns,
prefix: getDataPathPrefix(indexRows, indexColumns),
});
const rawData = get(this.indexesData, path);
if (rawData) {
// 如果已经有数据则取已有数据
return CellData.getCellData(rawData, query[EXTRA_FIELD]);
}
if (isTotals) {
return this.getTotalValue(query, totalStatus);
}
}
getQueryExtraFields(query) {
const { values = [] } = this.fields;
const extra = query[EXTRA_FIELD];
if (extra) {
return includes(values, extra) ? [extra] : [];
}
return values;
}
getCellMultiData(params) {
const { query = {}, queryType = QueryDataType.All, drillDownFields = [], } = params || {};
if (isEmpty(query)) {
// 如果查询的 query 为空,这样的场景其实没有意义,如果用户想获取全量数据,可以直接从 data 中获取
// eslint-disable-next-line no-console
console.warn(`query: ${query} shouldn't be empty, you can get all data from dataCfg if you're intended.\n you should use { EXTRA_FIELD: xxx} as least if you want query all specific value data`);
}
const { rows, columns } = this.fields;
const totalRows = !isEmpty(drillDownFields)
? rows.concat(drillDownFields)
: rows;
const indexRows = getIndexFields(totalRows);
const indexColumns = getIndexFields(columns);
const rowDimensionValues = transformDimensionsValues(query, indexRows, MULTI_VALUE);
const colDimensionValues = transformDimensionsValues(query, indexColumns, MULTI_VALUE);
const { rowQueries, colQueries } = getFlattenDimensionValues({
rowDimensionValues,
colDimensionValues,
rowPivotMeta: this.rowPivotMeta,
colPivotMeta: this.colPivotMeta,
rowFields: indexRows,
colFields: indexColumns,
sortedDimensionValues: this.sortedDimensionValues,
queryType,
});
const prefix = getDataPathPrefix(indexRows, indexColumns);
const all = [];
for (const rowQuery of rowQueries) {
for (const colQuery of colQueries) {
const path = getDataPath({
rowDimensionValues: rowQuery,
colDimensionValues: colQuery,
rowPivotMeta: this.rowPivotMeta,
colPivotMeta: this.colPivotMeta,
rowFields: indexRows,
colFields: indexColumns,
prefix,
});
let hadMultiField = false;
let result = this.indexesData;
for (let i = 0; i < path.length; i++) {
const current = path[i];
if (hadMultiField) {
if (isMultiValue(current)) {
result = flattenIndexesData(result, queryType);
}
else {
result = compact(map(result, (item) => item === null || item === void 0 ? void 0 : item[current]));
}
}
else if (isMultiValue(current)) {
hadMultiField = true;
result = compact([result]);
i--;
}
else {
result = result === null || result === void 0 ? void 0 : result[current];
}
}
// 如果每一个维度都是被指定好的,那么最终获取的数据就是单个的
if (isArray(result)) {
all.push(...result);
}
else if (result) {
all.push(result);
}
}
}
const extraFields = this.getQueryExtraFields(query);
// 多个 extra field 有时对应的同一个对象,需要进行去重
return flatMap(uniq(all), (item) => {
return item ? CellData.getCellDataList(item, extraFields) : [];
});
}
getFieldFormatter(field, cellMeta) {
// 兼容总计小计场景
if (field === TOTAL_VALUE) {
return this.getFieldFormatterForTotalValue(cellMeta);
}
return super.getFieldFormatter(field);
}
getFieldFormatterForTotalValue(cellMeta) {
let valueField = '';
// 当数据置于行头时,小计总计列尝试去找对应的指标
if (!this.spreadsheet.isValueInCols() && cellMeta) {
valueField = get(cellMeta.rowQuery, EXTRA_FIELD);
}
// 如果没有找到对应指标,则默认取第一个维度
valueField = valueField !== null && valueField !== void 0 ? valueField : get(this.fields.values, 0);
return super.getFieldFormatter(valueField);
}
/**
* 自定义度量组位置值
* @param customValueOrder 用户配置度量组位置,从 0 开始
* @param fields Rows || Columns
*/
handleCustomMeasuresOrder(customValueOrder, fields) {
const newFields = uniq([...fields]);
if (fields.length >= customValueOrder) {
newFields.splice(customValueOrder, 0, EXTRA_FIELD);
return newFields;
}
// 当用户配置的度量组位置大于等于度量组数量时,默认放在最后
return [...newFields, EXTRA_FIELD];
}
// 是否开启自定义度量组位置值
isCustomMeasuresPosition(customValueOrder) {
return isNumber(customValueOrder);
}
getRowData(cellMeta) {
return this.getCellMultiData({ query: cellMeta.rowQuery });
}
}
//# sourceMappingURL=pivot-data-set.js.map