@antv/s2
Version:
effective spreadsheet render core lib
420 lines • 16.2 kB
JavaScript
import { compact, find, flatMap, forEach, get, intersection, isArray, isEmpty, isNull, isString, last, set, } from 'lodash';
import { EMPTY_EXTRA_FIELD_PLACEHOLDER, EXTRA_FIELD, MULTI_VALUE, NODE_ID_SEPARATOR, QueryDataType, ROOT_NODE_ID, TOTAL_VALUE, } from '../../common/constant';
import { generateNillString } from '../layout/generate-id';
export function filterExtraDimension(dimensions = []) {
return dimensions.filter((d) => d !== EXTRA_FIELD);
}
export function isMultiValue(pathValue) {
return pathValue === MULTI_VALUE;
}
/**
* Transform from origin single data to correct dimension values
* data: {
* price: 16,
* province: '辽宁省',
* city: '芜湖市',
* category: '家具',
* subCategory: '椅子',
* }
* dimensions: [province, city]
* return [辽宁省, 芜湖市]
*
* @param record
* @param dimensions
*/
export function transformDimensionsValues(record = {}, dimensions = [], placeholder = TOTAL_VALUE) {
return dimensions.reduce((res, dimension) => {
const value = record[dimension];
if (!(dimension in record)) {
res.push(placeholder);
}
else {
res.push(generateNillString(value));
}
return res;
}, []);
}
export function getExistValues(data, values) {
const result = values.filter((v) => v in data);
if (isEmpty(result)) {
result.push(EMPTY_EXTRA_FIELD_PLACEHOLDER);
}
return result;
}
function transformDimensionsValuesWithExtraFields(record = {}, dimensions = [], values) {
const result = [];
function transform(data, fields, valueField) {
return fields.reduce((res, dimension) => {
const value = data[dimension];
if (!(dimension in data)) {
if (dimension === EXTRA_FIELD && valueField) {
res.push(valueField);
}
else {
res.push(TOTAL_VALUE);
}
}
else {
res.push(generateNillString(value));
}
return res;
}, []);
}
if (values) {
values.forEach((value) => {
result.push(transform(record, dimensions, value));
});
}
else {
result.push(transform(record, dimensions));
}
return result;
}
/**
* Get dimensions with parent path
* field: 'category'
* defaultDimensions: ['province', 'city', 'category', 'subCategory']
* dimensions: [
* {
* province: '辽宁省',
* city: '芜湖市',
* category: '家具',
* subCategory: '椅子',
* price: ''
* },
* ]
* return ['辽宁省[&]芜湖市[&]家具']
*
* @param field
* @param defaultDimensions
* @param dimensions
*/
export function getDimensionsWithParentPath(field, defaultDimensions = [], dimensions) {
var _a;
const measure = defaultDimensions.slice(0, defaultDimensions.indexOf(field) + 1);
return (_a = dimensions
.map((item) => measure.map((i) => item.getValueByField(i)).join(`${NODE_ID_SEPARATOR}`))) === null || _a === void 0 ? void 0 : _a.filter(Boolean);
}
export function getDataPathPrefix(rowFields, colFields) {
return rowFields
.concat(colFields)
.filter((i) => i !== EXTRA_FIELD)
.join(NODE_ID_SEPARATOR);
}
/**
* Transform a single data to path
* {
* $$VALUE$$: 15
* $$EXTRA$$: 'price'
* "price": 15,
* "province": "辽宁省",
* "city": "达州市",
* "category": "家具",
* "subCategory": "椅子"
* }
* rows: [province, city]
* columns: [category, subCategory, $$EXTRA$$]
*
* rowDimensionValues = [辽宁省, 达州市]
* colDimensionValues = [家具, 椅子, price]
*
* @param params
*/
export function getDataPath(params) {
const { rowDimensionValues, colDimensionValues, isFirstCreate, onFirstCreate, rowFields, colFields, rowPivotMeta, colPivotMeta, prefix = '', } = params;
// 根据行、列维度值生成对应的 path 路径,始终将总计小计置于第 0 位,明细数据从第 1 位开始,有两个情况:
// 如果是汇总格子: path = [0, 0, 0, 0] path 中会存在 0 的值
// 如果是明细格子: path = [1, 1, 1] 数字均不为 0
const getPath = (dimensions, dimensionValues, pivotMeta, careRepeated) => {
let currentMeta = pivotMeta;
const path = [];
for (let i = 0; i < dimensionValues.length; i++) {
const value = dimensionValues[i];
if (isFirstCreate && currentMeta && !(currentMeta === null || currentMeta === void 0 ? void 0 : currentMeta.has(value))) {
const currentDimensions = dimensionValues
.slice(0, i + 1)
.map((dimensionValue) => generateNillString(dimensionValue));
const id = currentDimensions.join(NODE_ID_SEPARATOR);
const isTotal = value === TOTAL_VALUE;
let level;
if (isTotal) {
level = 0;
}
else if (currentMeta.has(TOTAL_VALUE)) {
level = currentMeta.size;
}
else {
level = currentMeta.size + 1;
}
currentMeta.set(value, {
id,
dimensions: currentDimensions,
value,
level,
children: new Map(),
});
onFirstCreate === null || onFirstCreate === void 0 ? void 0 : onFirstCreate({
dimension: dimensions === null || dimensions === void 0 ? void 0 : dimensions[i],
dimensionPath: id,
careRepeated,
});
}
const meta = currentMeta === null || currentMeta === void 0 ? void 0 : currentMeta.get(value);
path.push(isMultiValue(value) ? value : meta === null || meta === void 0 ? void 0 : meta.level);
if (meta) {
const childDimension = dimensions === null || dimensions === void 0 ? void 0 : dimensions[i + 1];
if (isFirstCreate && meta.childField !== childDimension) {
// mark the child field
// NOTE: should take more care when reset meta.childField to undefined, the meta info is shared with brother nodes.
meta.childField = childDimension;
}
currentMeta = meta === null || meta === void 0 ? void 0 : meta.children;
}
}
return path;
};
const rowPath = getPath(rowFields, rowDimensionValues, rowPivotMeta, false);
const colPath = getPath(colFields, colDimensionValues, colPivotMeta, true);
return [prefix, ...rowPath, ...colPath];
}
/**
* 获取用于数据 transform 中定位的 string 的字段,自定义布局中,自定义字段是 object 类型,这些类型不应该参与到数据处理的流程中
*/
export function getIndexFields(fields = []) {
return fields.filter(isString);
}
/**
* 转换原始数据为二维数组数据
*/
export function transformIndexesData(params) {
const { rows = [], columns = [], values, valueInCols, data = [], indexesData = {}, sortedDimensionValues, rowPivotMeta, colPivotMeta, getExistValuesByDataItem, } = params;
const paths = [];
/**
* 记录行头、列头重复的字段
*/
const repeatedDimensionSet = new Set(intersection(rows, columns));
/**
* 在 PivotMap 创建新节点时,填充 sortedDimensionValues 维度数据
*/
const onFirstCreate = ({ dimension, dimensionPath, careRepeated = true, }) => {
if (careRepeated && repeatedDimensionSet.has(dimension)) {
// 当行、列都配置了同一维度字段时,因为 getDataPath 先处理行、再处理列
// 所有重复字段的维度值无需再加入到 sortedDimensionValues
return;
}
(sortedDimensionValues[dimension] ||
(sortedDimensionValues[dimension] = [])).push(dimensionPath);
};
const prefix = getDataPathPrefix(rows, columns);
data.forEach((item) => {
// 空数据没有意义,直接跳过
if (!item || isEmpty(item)) {
return;
}
const existValues = getExistValuesByDataItem
? getExistValuesByDataItem(item, values)
: getExistValues(item, values);
const multiRowDimensionValues = transformDimensionsValuesWithExtraFields(item, rows, valueInCols ? null : existValues);
const multiColDimensionValues = transformDimensionsValuesWithExtraFields(item, columns, valueInCols ? existValues : null);
for (const rowDimensionValues of multiRowDimensionValues) {
for (const colDimensionValues of multiColDimensionValues) {
const path = getDataPath({
rowDimensionValues,
colDimensionValues,
rowPivotMeta: rowPivotMeta,
colPivotMeta: colPivotMeta,
rowFields: rows,
colFields: columns,
isFirstCreate: true,
onFirstCreate,
prefix,
});
paths.push(path);
set(indexesData, path, item);
}
}
});
return {
paths,
indexesData,
rowPivotMeta: rowPivotMeta,
colPivotMeta: colPivotMeta,
sortedDimensionValues,
};
}
export function deleteMetaById(meta, nodeId) {
if (!meta || !nodeId) {
return;
}
const paths = nodeId.split(NODE_ID_SEPARATOR);
const deletePath = last(paths);
let currentMeta = meta;
forEach(paths, (path, idx) => {
const pathMeta = currentMeta.get(path);
if (pathMeta) {
if (path === deletePath) {
pathMeta.children = new Map();
pathMeta.childField = undefined;
}
else {
currentMeta = pathMeta.children;
}
return true;
}
// exit iteration early when pathMeta not exists
return idx === 0 && path === ROOT_NODE_ID;
});
}
export function generateExtraFieldMeta(meta, cornerExtraFieldText, defaultText) {
const valueFormatter = (value) => {
const currentMeta = find(meta, ({ field }) => field === value);
return get(currentMeta, 'name', value);
};
// 虚拟列字段,为文本分类字段
const extraFieldName = cornerExtraFieldText || defaultText;
const extraFieldMeta = {
field: EXTRA_FIELD,
name: extraFieldName,
formatter: (value) => valueFormatter(value),
};
return extraFieldMeta;
}
export function getHeaderTotalStatus(row, col) {
return {
isRowGrandTotal: row.isGrandTotals,
isRowSubTotal: row.isSubTotals,
isColGrandTotal: col.isGrandTotals,
isColSubTotal: col.isSubTotals,
};
}
/**
* 检查 getMultiData 时,传入的 query 是否是包含总计、小计分组的场景
* MULTI_VALUE 后面再出现具体的名字,就表明是分组场景
* 以 rows: [province, city] 为例
* 如果是: [四川, MULTI_VALUE] => 代表获取四川下面的所有 city
* 如果是: [MULTI_VALUE, 成都] => 这种结果就是所有成都的小计分组
* 每个 province 下面的 city 都不一样的
* 就算换成 [province, sex] => [MULTI_VALUE, 女] 这样的形式,去获取所有 province 下的女性,但是每个 province 下的女性的 index 也可能不同
* 需要将其拓展成多个结构 => [MULTI_VALUE, 女] => [[四川,女], [北京,女], ....] => [[1,1],[2,1],[3,2]....]
*/
export function existDimensionTotalGroup(path) {
let multiIdx = null;
for (let i = 0; i < path.length; i++) {
const element = path[i];
if (isMultiValue(element)) {
multiIdx = i;
}
else if (!isNull(multiIdx) && multiIdx < i) {
return true;
}
}
return false;
}
export function getSatisfiedPivotMetaValues(params) {
const { pivotMeta, dimensionValues, fieldIdx, queryType, fields, sortedDimensionValues, } = params;
const rootContainer = {
children: pivotMeta,
};
let metaValueList = [rootContainer];
function flattenMetaValue(list, field) {
var _a;
const allValues = flatMap(list, (metaValue) => {
const values = [];
for (const v of metaValue.children.values()) {
if (v.value !== EMPTY_EXTRA_FIELD_PLACEHOLDER &&
(queryType === QueryDataType.All ? true : v.value !== TOTAL_VALUE)) {
values.push(v);
}
}
return values;
});
if (list.length > 1) {
// 从不同父维度中获取的子维度需要再排一次,比如province => city 按照字母倒序,那么在获取了所有 province 的 city 后需要再排一次
const sortedDimensionValue = (_a = sortedDimensionValues[field]) !== null && _a !== void 0 ? _a : [];
const indexMap = new Map(sortedDimensionValue.map((id, index) => [id, index]));
allValues.sort((a, b) => { var _a, _b; return ((_a = indexMap.get(a.id)) !== null && _a !== void 0 ? _a : 0) - ((_b = indexMap.get(b.id)) !== null && _b !== void 0 ? _b : 0); });
return allValues;
}
return allValues;
}
for (let i = 0; i <= fieldIdx; i++) {
const dimensionValue = dimensionValues[i];
const field = fields[i];
if (isMultiValue(dimensionValue)) {
metaValueList = flattenMetaValue(metaValueList, field);
}
else {
metaValueList = metaValueList
.map((v) => v.children.get(dimensionValue))
.filter(Boolean);
}
}
return metaValueList;
}
export function flattenDimensionValues(params) {
const { dimensionValues, pivotMeta, fields, sortedDimensionValues, queryType = QueryDataType.All, } = params;
if (!existDimensionTotalGroup(dimensionValues)) {
return [dimensionValues];
}
const metaValues = getSatisfiedPivotMetaValues({
pivotMeta,
dimensionValues,
fieldIdx: dimensionValues.length - 1,
queryType,
fields,
sortedDimensionValues,
});
return metaValues.map((v) => v.dimensions);
}
export function getFlattenDimensionValues(params) {
const { rowFields, rowDimensionValues, rowPivotMeta, colFields, colDimensionValues, colPivotMeta, queryType, sortedDimensionValues, } = params;
const rowQueries = flattenDimensionValues({
dimensionValues: rowDimensionValues,
pivotMeta: rowPivotMeta,
fields: rowFields,
sortedDimensionValues,
queryType,
});
const colQueries = flattenDimensionValues({
dimensionValues: colDimensionValues,
pivotMeta: colPivotMeta,
fields: colFields,
sortedDimensionValues,
queryType,
});
return {
rowQueries,
colQueries,
};
}
export function flattenIndexesData(data, queryType) {
if (!data) {
return [];
}
if (!isArray(data)) {
return compact([data]);
}
return flatMap(data, (dimensionData) => {
if (!isArray(dimensionData)) {
return compact([dimensionData]);
}
// 数组的第 0 项是总计/小计专位,从第 1 项开始是明细数据
const startIdx = queryType === QueryDataType.DetailOnly ? 1 : 0;
return compact(dimensionData.slice(startIdx));
});
}
/**
* Get dimensions without path pre
* dimensions: ['辽宁省[&]芜湖市[&]家具[&]椅子']
* return ['椅子']
*
* @param dimensions
*/
export function getDimensionsWithoutPathPre(dimensions) {
return dimensions.map((item) => {
var _a;
const splitArr = item === null || item === void 0 ? void 0 : item.split(NODE_ID_SEPARATOR);
return (_a = splitArr[(splitArr === null || splitArr === void 0 ? void 0 : splitArr.length) - 1]) !== null && _a !== void 0 ? _a : item;
});
}
//# sourceMappingURL=pivot-data-set.js.map