UNPKG

@antv/s2

Version:

effective spreadsheet render core lib

450 lines 20.7 kB
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