diff --git a/packages/s2-core/__tests__/spreadsheet/custom-grid-spec.ts b/packages/s2-core/__tests__/spreadsheet/custom-grid-spec.ts index d28cd7025a..6b2f946411 100644 --- a/packages/s2-core/__tests__/spreadsheet/custom-grid-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/custom-grid-spec.ts @@ -5,7 +5,11 @@ import { pick } from 'lodash'; import { CustomGridData } from 'tests/data/data-custom-grid'; import { waitForRender } from 'tests/util'; import { getContainer } from 'tests/util/helpers'; -import { KEY_GROUP_COL_RESIZE_AREA } from '../../src/common/constant'; +import { + KEY_GROUP_COL_RESIZE_AREA, + VALUE_FIELD, +} from '../../src/common/constant'; +import { Aggregation } from '../../src/common/interface/basic'; import { CustomGridPivotDataSet } from '../../src/data-set/custom-grid-pivot-data-set'; import { customColGridSimpleFields, @@ -538,5 +542,51 @@ describe('SpreadSheet Custom Grid Tests', () => { s2.facet.getColNodes().some((node) => node.isCollapsed), ).toBeFalsy(); }); + + // https://github.com/antvis/S2/issues/2893 + test.each(['tree', 'grid'])( + 'should render correct total node for %s mode', + async (hierarchyType) => { + s2.setOptions({ + hierarchyType, + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + reverseGrandTotalsLayout: true, + reverseSubTotalsLayout: true, + subTotalsDimensions: ['type'], + calcGrandTotals: { + aggregation: Aggregation.SUM, + }, + calcSubTotals: { + aggregation: Aggregation.SUM, + }, + }, + col: { + showGrandTotals: true, + showSubTotals: true, + reverseGrandTotalsLayout: true, + reverseSubTotalsLayout: true, + subTotalsDimensions: ['type'], + calcGrandTotals: { + aggregation: Aggregation.SUM, + }, + calcSubTotals: { + aggregation: Aggregation.SUM, + }, + }, + }, + }); + + await s2.render(false); + + expect(s2.facet.getRowGrandTotalsNodes()).toHaveLength(1); + expect(s2.facet.getColGrandTotalsNodes()).toHaveLength(0); + + expect(s2.facet.getCellMeta(0, 0).data[VALUE_FIELD]).toEqual(24); + expect(s2.facet.getCellMeta(0, 1).data[VALUE_FIELD]).toEqual(10); + }, + ); }); }); diff --git a/packages/s2-core/__tests__/spreadsheet/custom-table-col-spec.ts b/packages/s2-core/__tests__/spreadsheet/custom-table-col-spec.ts index f1ebace5cc..9c84fffe1b 100644 --- a/packages/s2-core/__tests__/spreadsheet/custom-table-col-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/custom-table-col-spec.ts @@ -308,4 +308,29 @@ describe('TableSheet Custom Tests', () => { expect(resizeAreaList.length).toEqual(8); }); + + test.each([ + { showDefaultHeaderActionIcon: false }, + { showDefaultHeaderActionIcon: true }, + ])( + 'should render correctly sort action icon in value cell for custom col header with %o', + async (options) => { + s2.setOptions(options); + + await s2.render(false); + + const fields = s2.facet + .getColCells() + .filter((cell) => { + return cell.getActionIcons().length >= 1; + }) + .map((cell) => cell.getMeta().field); + + expect(fields).toEqual( + options.showDefaultHeaderActionIcon + ? ['province', 'city', 'type', 'price', 'number'] + : [], + ); + }, + ); }); diff --git a/packages/s2-core/__tests__/spreadsheet/custom-tree-spec.ts b/packages/s2-core/__tests__/spreadsheet/custom-tree-spec.ts index 2c93df3010..f2a256984d 100644 --- a/packages/s2-core/__tests__/spreadsheet/custom-tree-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/custom-tree-spec.ts @@ -1,7 +1,6 @@ import type { S2DataConfig, S2Options } from '@/common/interface'; import { PivotSheet, SpreadSheet } from '@/sheet-type'; import { getContainer } from 'tests/util/helpers'; -import { CustomTreePivotDataSet } from '../../src'; import type { HeaderCell } from '../../src/cell/header-cell'; import { customRowGridSimpleFields } from '../data/custom-grid-simple-fields'; import { customTreeNodes } from '../data/custom-tree-nodes'; @@ -81,10 +80,6 @@ describe('SpreadSheet Custom Tree Tests', () => { expect(s2.dataSet.fields.valueInCols).toBeFalsy(); }); - test('should use custom tree pivot dataSet', () => { - expect(s2.dataSet).toBeInstanceOf(CustomTreePivotDataSet); - }); - test('should get correctly dataset fields', () => { expect(s2.dataSet.fields).toMatchSnapshot(); }); diff --git a/packages/s2-core/__tests__/unit/data-set/custom-tree-data-set-spec.ts b/packages/s2-core/__tests__/unit/data-set/custom-grid-data-set-spec.ts similarity index 85% rename from packages/s2-core/__tests__/unit/data-set/custom-tree-data-set-spec.ts rename to packages/s2-core/__tests__/unit/data-set/custom-grid-data-set-spec.ts index 4c7a8110f3..fe6cdf9f83 100644 --- a/packages/s2-core/__tests__/unit/data-set/custom-tree-data-set-spec.ts +++ b/packages/s2-core/__tests__/unit/data-set/custom-grid-data-set-spec.ts @@ -3,11 +3,12 @@ */ import { EXTRA_FIELD, ORIGIN_FIELD } from '@/common/constant'; import type { S2DataConfig } from '@/common/interface'; -import { CustomTreePivotDataSet } from '@/data-set/custom-tree-pivot-data-set'; +import { CustomGridPivotDataSet } from '@/data-set/custom-grid-pivot-data-set'; import { PivotSheet } from '@/sheet-type'; import { get } from 'lodash'; import { customTreeNodes } from 'tests/data/custom-tree-nodes'; import { CustomTreeData } from 'tests/data/data-custom-tree'; +import { Store } from '../../../src'; jest.mock('@/sheet-type'); @@ -15,7 +16,7 @@ jest.mock('@/interaction/root'); const MockPivotSheet = PivotSheet as unknown as jest.Mock; -describe('Custom Tree Dataset Test', () => { +describe('Custom Grid Dataset Test', () => { const values = [ 'measure-a', 'measure-b', @@ -35,14 +36,21 @@ describe('Custom Tree Dataset Test', () => { }, }; - const mockSheet = new MockPivotSheet(); - const dataSet = new CustomTreePivotDataSet(mockSheet); + let dataSet: CustomGridPivotDataSet; - dataSet.setDataCfg(dataCfg); + beforeEach(() => { + const mockSheet = new MockPivotSheet(); + + mockSheet.isCustomRowFields = () => true; + mockSheet.store = new Store(); + + dataSet = new CustomGridPivotDataSet(mockSheet); + dataSet.setDataCfg(dataCfg); + }); describe('test base dataset structure', () => { test('should get correct field data', () => { - expect(dataSet.fields.rows).toEqual([EXTRA_FIELD]); + expect(dataSet.fields.rows).toEqual([...customTreeNodes, EXTRA_FIELD]); expect(dataSet.fields.columns).toEqual(['type', 'sub_type']); expect(dataSet.fields.values).toEqual(values); }); diff --git a/packages/s2-core/src/cell/table-col-cell.ts b/packages/s2-core/src/cell/table-col-cell.ts index a907be4bec..584c568110 100644 --- a/packages/s2-core/src/cell/table-col-cell.ts +++ b/packages/s2-core/src/cell/table-col-cell.ts @@ -116,7 +116,16 @@ export class TableColCell extends ColCell { } protected showSortIcon() { - return this.spreadsheet.options.showDefaultHeaderActionIcon; + const { extra } = this.meta; + const { options } = this.spreadsheet; + const { showDefaultHeaderActionIcon } = options; + + if (!extra?.isCustomNode) { + return showDefaultHeaderActionIcon; + } + + // 自定义列头时, 只在叶子节点展示 + return showDefaultHeaderActionIcon && this.meta.isLeaf; } protected getTextStyle() { diff --git a/packages/s2-core/src/data-set/custom-grid-pivot-data-set.ts b/packages/s2-core/src/data-set/custom-grid-pivot-data-set.ts index 2ae06d0b1b..7fcd12a3f5 100644 --- a/packages/s2-core/src/data-set/custom-grid-pivot-data-set.ts +++ b/packages/s2-core/src/data-set/custom-grid-pivot-data-set.ts @@ -1,14 +1,12 @@ import { EXTRA_FIELD, i18n } from '../common'; import type { S2DataConfig } from '../common/interface'; -import { CustomTreePivotDataSet } from './custom-tree-pivot-data-set'; +import { PivotDataSet } from './pivot-data-set'; -export class CustomGridPivotDataSet extends CustomTreePivotDataSet { +export class CustomGridPivotDataSet extends PivotDataSet { processDataCfg(dataCfg: S2DataConfig): S2DataConfig { const valueInCols = !this.spreadsheet.isCustomRowFields(); const originalRows = dataCfg.fields.rows || []; - const rows = valueInCols - ? [...originalRows] - : [...originalRows, EXTRA_FIELD]; + const rows = valueInCols ? originalRows : [...originalRows, EXTRA_FIELD]; const meta = this.processMeta(dataCfg.meta!, i18n('数值')); return { diff --git a/packages/s2-core/src/data-set/custom-tree-pivot-data-set.ts b/packages/s2-core/src/data-set/custom-tree-pivot-data-set.ts deleted file mode 100644 index 229989b430..0000000000 --- a/packages/s2-core/src/data-set/custom-tree-pivot-data-set.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { get, type PropertyPath } from 'lodash'; -import { EXTRA_FIELD } from '../common/constant'; -import { i18n } from '../common/i18n'; -import type { Meta, S2DataConfig } from '../common/interface'; -import { - getDataPath, - getDataPathPrefix, - transformDimensionsValues, -} from '../utils/dataset/pivot-data-set'; -import { CellData } from './cell-data'; -import type { GetCellDataParams } from './interface'; -import { PivotDataSet } from './pivot-data-set'; - -export class CustomTreePivotDataSet extends PivotDataSet { - getCellData(params: GetCellDataParams) { - const { query = {} } = params || {}; - const { columns, rows } = this.fields; - const rowDimensionValues = transformDimensionsValues( - query, - rows as string[], - ); - const colDimensionValues = transformDimensionsValues( - query, - columns as string[], - ); - const path = getDataPath({ - rowDimensionValues, - colDimensionValues, - rowPivotMeta: this.rowPivotMeta, - colPivotMeta: this.colPivotMeta, - rowFields: rows as string[], - colFields: columns as string[], - prefix: getDataPathPrefix(rows as string[], columns as string[]), - }); - - const rawData = get(this.indexesData, path as PropertyPath); - - if (rawData) { - return CellData.getCellData(rawData, query[EXTRA_FIELD]); - } - } - - processDataCfg(dataCfg: S2DataConfig): S2DataConfig { - /** - * 自定义行头有如下几个特点 - * 1、rows配置必须是空,需要额外添加 $$extra$$ 定位数据(标记指标的id) - * 2、要有配置 fields.rowCustomTree(行头结构) - * 3、values 不需要参与计算,默认就在行头结构中 - */ - - const updatedDataCfg = super.processDataCfg(dataCfg); - const newMeta: Meta[] = this.processMeta(dataCfg.meta, i18n('指标')); - - return { - ...updatedDataCfg, - meta: newMeta, - fields: { - ...updatedDataCfg.fields, - rows: [EXTRA_FIELD], - valueInCols: false, - }, - }; - } -} diff --git a/packages/s2-core/src/data-set/index.ts b/packages/s2-core/src/data-set/index.ts index f0ffc95172..1e498c7cd4 100644 --- a/packages/s2-core/src/data-set/index.ts +++ b/packages/s2-core/src/data-set/index.ts @@ -1,17 +1,10 @@ import { BaseDataSet } from './base-data-set'; import { CustomGridPivotDataSet } from './custom-grid-pivot-data-set'; -import { CustomTreePivotDataSet } from './custom-tree-pivot-data-set'; import { PivotDataSet } from './pivot-data-set'; import { TableDataSet } from './table-data-set'; export { CellData } from './cell-data'; -export { - BaseDataSet, - CustomGridPivotDataSet, - CustomTreePivotDataSet, - PivotDataSet, - TableDataSet, -}; +export { BaseDataSet, CustomGridPivotDataSet, PivotDataSet, TableDataSet }; export * from './interface'; diff --git a/packages/s2-core/src/data-set/pivot-data-set.ts b/packages/s2-core/src/data-set/pivot-data-set.ts index 240e691945..5907f76b5f 100644 --- a/packages/s2-core/src/data-set/pivot-data-set.ts +++ b/packages/s2-core/src/data-set/pivot-data-set.ts @@ -54,6 +54,7 @@ import { getDataPathPrefix, getExistValues, getFlattenDimensionValues, + getIndexFields, getSatisfiedPivotMetaValues, isMultiValue, transformDimensionsValues, @@ -104,22 +105,21 @@ export class PivotDataSet extends BaseDataSet { this.rowPivotMeta = new Map(); this.colPivotMeta = new Map(); this.dimensionValuesCache = new Map(); - this.transformIndexesData(this.originData, rows as string[]); + this.transformIndexesData(this.originData, rows); this.handleDimensionValuesSort(); } public transformIndexesData( data: RawData[], - rows: string[], + rows?: CustomHeaderFields, ): TransformResult { const { columns, values, valueInCols } = this.fields; - let result!: TransformResult; DebuggerUtil.getInstance().debugCallback(DEBUG_TRANSFORM_DATA, () => { result = transformIndexesData({ - rows: rows as string[], - columns: columns as string[], + rows: getIndexFields(rows), + columns: getIndexFields(columns), values: values!, valueInCols: valueInCols!, data, @@ -349,14 +349,14 @@ export class PivotDataSet extends BaseDataSet { if (rows.includes(field)) { return { - dimensions: rows as string[], + dimensions: getIndexFields(rows), pivotMeta: this.rowPivotMeta, }; } if (columns.includes(field)) { return { - dimensions: columns as string[], + dimensions: getIndexFields(columns), pivotMeta: this.colPivotMeta, }; } @@ -444,7 +444,7 @@ export class PivotDataSet extends BaseDataSet { const { query = {}, rowNode, isTotals = false, totalStatus } = params || {}; const { rows: originRows, columns } = this.fields; - let rows = originRows as string[]; + let rows = originRows; const drillDownIdPathMap = this.spreadsheet?.store.get('drillDownIdPathMap'); @@ -458,23 +458,23 @@ export class PivotDataSet extends BaseDataSet { // 如果是下钻结点,行维度在 originRows 中并不存在 if (rowNode && isDrillDown) { - rows = - Node.getFieldPath(rowNode, isDrillDown) ?? (originRows as string[]); + rows = Node.getFieldPath(rowNode, isDrillDown) ?? originRows; } - const rowDimensionValues = transformDimensionsValues(query, rows); - const colDimensionValues = transformDimensionsValues( - query, - columns as string[], - ); + 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: rows as string[], - colFields: columns as string[], - prefix: getDataPathPrefix(rows as string[], columns as string[]), + rowFields: indexRows, + colFields: indexColumns, + prefix: getDataPathPrefix(indexRows, indexColumns), }); const rawData = get(this.indexesData, path as PropertyPath); @@ -491,21 +491,27 @@ export class PivotDataSet extends BaseDataSet { public getTotalStatus = (query: Query): TotalStatus => { const { columns, rows } = this.fields; - const isTotals = (dimensions: string[], isSubTotal?: boolean) => { + const isTotals = ( + dimensions?: CustomHeaderFields, + isSubTotal?: boolean, + ) => { if (isSubTotal) { - const firstDimension = find(dimensions, (item) => !has(query, item)); + const firstDimension = find( + dimensions, + (item) => !has(query, item as string), + ); return !!(firstDimension && firstDimension !== first(dimensions)); } - return every(dimensions, (item) => !has(query, item)); + return every(dimensions, (item) => !has(query, item as string)); }; return { - isRowGrandTotal: isTotals(filterExtraDimension(rows as string[])), - isRowSubTotal: isTotals(rows as string[], true), - isColGrandTotal: isTotals(filterExtraDimension(columns as string[])), - isColSubTotal: isTotals(columns as string[], true), + isRowGrandTotal: isTotals(filterExtraDimension(rows)), + isRowSubTotal: isTotals(rows, true), + isColGrandTotal: isTotals(filterExtraDimension(columns)), + isColSubTotal: isTotals(columns, true), }; }; @@ -536,18 +542,21 @@ export class PivotDataSet extends BaseDataSet { } const { rows, columns } = this.fields; - const totalRows: string[] = !isEmpty(drillDownFields) - ? (rows as string[]).concat(drillDownFields!) - : (rows as string[]); + const totalRows = !isEmpty(drillDownFields) + ? rows!.concat(drillDownFields!) + : rows!; + + const indexRows = getIndexFields(totalRows); + const indexColumns = getIndexFields(columns); const rowDimensionValues = transformDimensionsValues( query, - totalRows, + indexRows, MULTI_VALUE, ); const colDimensionValues = transformDimensionsValues( query, - columns as string[], + indexColumns, MULTI_VALUE, ); @@ -556,13 +565,13 @@ export class PivotDataSet extends BaseDataSet { colDimensionValues, rowPivotMeta: this.rowPivotMeta, colPivotMeta: this.colPivotMeta, - rowFields: totalRows, - colFields: columns as string[], + rowFields: indexRows, + colFields: indexColumns, sortedDimensionValues: this.sortedDimensionValues, queryType, }); - const prefix = getDataPathPrefix(totalRows, columns as string[]); + const prefix = getDataPathPrefix(indexRows, indexColumns); const all: RawData[] = []; for (const rowQuery of rowQueries) { @@ -572,8 +581,8 @@ export class PivotDataSet extends BaseDataSet { colDimensionValues: colQuery, rowPivotMeta: this.rowPivotMeta, colPivotMeta: this.colPivotMeta, - rowFields: totalRows, - colFields: columns as string[], + rowFields: indexRows, + colFields: indexColumns, prefix, }); diff --git a/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts b/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts index 87734069f8..53f32bf23c 100644 --- a/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts +++ b/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts @@ -114,7 +114,6 @@ const buildNormalGridHierarchy = (params: GridHeaderParams) => { } } - // add totals if needed addTotals({ currentField, lastField: fields[index - 1], diff --git a/packages/s2-core/src/sheet-type/pivot-sheet.ts b/packages/s2-core/src/sheet-type/pivot-sheet.ts index a9ffcd66ef..804a5b94f3 100644 --- a/packages/s2-core/src/sheet-type/pivot-sheet.ts +++ b/packages/s2-core/src/sheet-type/pivot-sheet.ts @@ -144,10 +144,10 @@ export class PivotSheet extends SpreadSheet { const { rows, columns } = this.dataCfg.fields; const { hideValue } = this.options.style!.colCell!; const sortField = this.isValueInCols() ? last(rows) : last(columns); - const { query, value } = meta; + const { query, field, value, extra } = meta; const sortQuery = clone(query); - let sortValue = value; + let sortValue = extra?.isCustomNode ? field : value; // 数值置于列头且隐藏了指标列头的情况, 会默认取第一个指标做组内排序, 需要还原指标列的 query, 所以多指标时请不要这么用…… if (hideValue && this.isValueInCols()) { diff --git a/packages/s2-core/src/utils/dataset/pivot-data-set.ts b/packages/s2-core/src/utils/dataset/pivot-data-set.ts index 015a9d2864..022eb8c7aa 100644 --- a/packages/s2-core/src/utils/dataset/pivot-data-set.ts +++ b/packages/s2-core/src/utils/dataset/pivot-data-set.ts @@ -8,10 +8,11 @@ import { isArray, isEmpty, isNull, + isString, last, set, } from 'lodash'; -import type { RawData } from '../../common'; +import type { CustomHeaderFields, RawData } from '../../common'; import { EMPTY_EXTRA_FIELD_PLACEHOLDER, EXTRA_FIELD, @@ -37,7 +38,7 @@ import type { import type { Node } from '../../facet/layout/node'; import { generateNillString } from '../layout/generate-id'; -export function filterExtraDimension(dimensions: string[] = []) { +export function filterExtraDimension(dimensions: CustomHeaderFields = []) { return dimensions.filter((d) => d !== EXTRA_FIELD); } @@ -89,7 +90,7 @@ export function getExistValues(data: RawData, values: string[]) { return result; } -export function transformDimensionsValuesWithExtraFields( +function transformDimensionsValuesWithExtraFields( record: RawData = {}, dimensions: string[] = [], values: string[] | null, @@ -293,13 +294,20 @@ export interface TransformResult { sortedDimensionValues: SortedDimensionValues; } +/** + * 获取用于数据 transform 中定位的 string 的字段,自定义布局中,自定义字段是 object 类型,这些类型不应该参与到数据处理的流程中 + */ +export function getIndexFields(fields: CustomHeaderFields = []) { + return fields.filter(isString); +} + /** * 转换原始数据为二维数组数据 */ export function transformIndexesData(params: Param): TransformResult { const { - rows, - columns, + rows = [], + columns = [], values, valueInCols, data = [], @@ -337,7 +345,7 @@ export function transformIndexesData(params: Param): TransformResult { ).push(dimensionPath); }; - const prefix = getDataPathPrefix(rows, columns as string[]); + const prefix = getDataPathPrefix(rows, columns); data.forEach((item: RawData) => { // 空数据没有意义,直接跳过 diff --git a/packages/s2-core/src/utils/sort-action.ts b/packages/s2-core/src/utils/sort-action.ts index 2d48cf58ef..171b00ceec 100644 --- a/packages/s2-core/src/utils/sort-action.ts +++ b/packages/s2-core/src/utils/sort-action.ts @@ -320,17 +320,17 @@ export const getSortByMeasureValues = ( const isSortFieldInRow = includes(fields.rows, sortFieldId); // 排序字段所在一侧的全部字段 const sortFields = filterExtraDimension( - (isSortFieldInRow ? fields.rows : columns) as string[], + isSortFieldInRow ? fields.rows : columns, ); // 与排序交叉的另一侧全部字段 const oppositeFields = filterExtraDimension( - (isSortFieldInRow ? columns : fields.rows) as string[], + isSortFieldInRow ? columns : fields.rows, ); const fieldAfterSortField = sortFields[sortFields.indexOf(sortFieldId) + 1]; const queryKeys = keys(query); const missedOppositeFields = oppositeFields.filter( - (field) => !queryKeys.includes(field), + (field) => !queryKeys.includes(field as string), ); const totalDataList = dataList.filter((dataItem) => { @@ -345,7 +345,7 @@ export const getSortByMeasureValues = ( return false; } - if (dataItemKeys.has(fieldAfterSortField)) { + if (dataItemKeys.has(fieldAfterSortField as string)) { /* * 若排序数据包含`排序字段`的后一个维度字段,则过滤 * 不需要比排序字段更 “明细” 的数据,只需取到 sortFieldId 当级的汇总 @@ -359,7 +359,7 @@ export const getSortByMeasureValues = ( * 如 query={ type: 'xx',EXTRA_FIELD=price },代表了最高可以取到 type 的小计汇总数据 */ const allMissed = missedOppositeFields.every( - (missedField) => !dataItemKeys.has(missedField), + (missedField) => !dataItemKeys.has(missedField as string), ); // 返回符合要求的汇总数据 diff --git a/packages/s2-react/playground/components/CustomGrid.tsx b/packages/s2-react/playground/components/CustomGrid.tsx index 65efe662ff..5951800dac 100644 --- a/packages/s2-react/playground/components/CustomGrid.tsx +++ b/packages/s2-react/playground/components/CustomGrid.tsx @@ -1,5 +1,10 @@ /* eslint-disable no-console */ -import type { S2DataConfig, SpreadSheet, ThemeCfg } from '@antv/s2'; +import { + Aggregation, + type S2DataConfig, + type SpreadSheet, + type ThemeCfg, +} from '@antv/s2'; import { customColGridFields, customRowGridFields, @@ -78,7 +83,6 @@ export const pivotSheetCustomColGridDataCfg: S2DataConfig = { enum CustomType { Row = 'row', Col = 'col', - All = 'all', } type CustomGridProps = Partial; @@ -122,9 +126,6 @@ export const CustomGrid = React.forwardRef( > 自定义行头 自定义列头 - - TODO: 自定义行头和列头 - ( setSheetType(checked ? 'pivot' : 'table'); }} /> + + { + setOptions({ + totals: { + row: { + showGrandTotals: checked, + showSubTotals: checked, + reverseGrandTotalsLayout: true, + reverseSubTotalsLayout: true, + subTotalsDimensions: ['type'], + calcGrandTotals: { + aggregation: Aggregation.SUM, + }, + calcSubTotals: { + aggregation: Aggregation.SUM, + }, + }, + }, + }); + }} + /> + { + setOptions({ + totals: { + col: { + showGrandTotals: checked, + showSubTotals: checked, + reverseGrandTotalsLayout: true, + reverseSubTotalsLayout: true, + subTotalsDimensions: ['type'], + calcGrandTotals: { + aggregation: Aggregation.SUM, + }, + calcSubTotals: { + aggregation: Aggregation.SUM, + }, + }, + }, + }); + }} + /> isObject(data[key])); diff --git a/packages/s2-react/src/hooks/useSpreadSheet.ts b/packages/s2-react/src/hooks/useSpreadSheet.ts index 86c8a16d1e..2b37860b41 100644 --- a/packages/s2-react/src/hooks/useSpreadSheet.ts +++ b/packages/s2-react/src/hooks/useSpreadSheet.ts @@ -124,7 +124,6 @@ export function useSpreadSheet(props: SheetComponentProps) { if (!Object.is(prevOptions, options)) { if (!Object.is(prevOptions?.hierarchyType, options?.hierarchyType)) { - // 自定义树目录需要重新构建 CustomTreePivotDataSet rebuildDataSet = true; reloadData = true; s2Ref.current?.setDataCfg(dataCfg); diff --git a/packages/s2-vue/src/hooks/useSheetUpdate.ts b/packages/s2-vue/src/hooks/useSheetUpdate.ts index 4eca76ad10..8cad721928 100644 --- a/packages/s2-vue/src/hooks/useSheetUpdate.ts +++ b/packages/s2-vue/src/hooks/useSheetUpdate.ts @@ -25,7 +25,6 @@ export const useSheetUpdate = ( updateFlag.rerender = true; if (!Object.is(prevOptions?.hierarchyType, options?.hierarchyType)) { - // 自定义树目录需要重新构建 CustomTreePivotDataSet updateFlag.reloadData = true; updateFlag.rebuildDataset = true; } diff --git a/s2-site/docs/common/totals.zh.md b/s2-site/docs/common/totals.zh.md index 63ddc33e36..8cd304edab 100644 --- a/s2-site/docs/common/totals.zh.md +++ b/s2-site/docs/common/totals.zh.md @@ -9,8 +9,8 @@ order: 3 | 参数 | 说明 | 类型 | 必选 | 默认值 | | ---- | ------ | --------------------------------------------- | ---- | ------ | -| row | 行总计配置 | [Total](/docs/api/general/S2Options#total) | | | -| col | 列总计配置 | [Total](/docs/api/general/S2Options#total) | | | +| row | 行总计配置(在 [自定义行头时](/manual/advanced/custom/custom-header#11-自定义行头) 无效) | [Total](#total) | - | | +| col | 列总计配置(在 [自定义列头时](/manual/advanced/custom/custom-header#12-自定义列头) 无效) | [Total](#total) | - | | ## Total diff --git a/s2-site/docs/manual/advanced/custom/custom-header.zh.md b/s2-site/docs/manual/advanced/custom/custom-header.zh.md index eaf4f38b0f..19a580315d 100644 --- a/s2-site/docs/manual/advanced/custom/custom-header.zh.md +++ b/s2-site/docs/manual/advanced/custom/custom-header.zh.md @@ -6,7 +6,16 @@ tag: New `S2` 默认提供 [平铺模式 (grid)](https://s2.antv.vision/zh/examples/basic/pivot#grid) 和 [树状模式 (tree)](https://s2.antv.vision/zh/examples/basic/pivot#tree) 两种**行头**单元格布局方式。 -默认通过**分组之后得到的数据生成层级结构**, 如果都不满足的话,可以通过自定义行列头,来定制你的目录结构,同样兼容平铺和树状这两种布局方式。 +默认通过**分组之后得到的数据生成层级结构**, 如果都不满足,还可以通过自定义行列头,来定制你的目录结构,同样兼容平铺和树状这两种布局方式。 + +## 一些限制 + +:::warning{title="注意"} + +1. 默认的排序 icon `showDefaultHeaderActionIcon` 无效。 +2. 自定义行头时,不支持配置行头[小计总计](/manual/basic/totals);自定义列头时,不支持配置列头[小计总计](/manual/basic/totals)。 + +::: ## 数据结构 diff --git a/s2-site/docs/manual/basic/sort/group.zh.md b/s2-site/docs/manual/basic/sort/group.zh.md index ef6fcac979..75ce2df535 100644 --- a/s2-site/docs/manual/basic/sort/group.zh.md +++ b/s2-site/docs/manual/basic/sort/group.zh.md @@ -9,7 +9,9 @@ tag: Updated `组内排序` 代表只影响一个分组内部的排序,例如下图中 `笔-价格` 选择 `组内升序` 时,`省份` 的排序方式不会更改,只会更改每个 `省份` 内部 `城市` 的顺序。 :::warning{title="注意"} -`行头/列头` 只存在单一状态,当前状态会覆盖前一状态,如上图所示,当对 `笔` 进行排序时,`纸张` 的排序状态消失,`行头 + 列头` 可同时存在自身状态。 + +1. `行头/列头` 只存在单一状态,当前状态会**覆盖前一状态**,如上图所示,当对 `笔` 进行排序时,`纸张` 的排序状态消失,`行头 + 列头` 可同时存在自身状态。 + ::: group-sort diff --git a/s2-site/docs/manual/basic/totals.zh.md b/s2-site/docs/manual/basic/totals.zh.md index 8f51a1ae2c..c8d3e47f81 100644 --- a/s2-site/docs/manual/basic/totals.zh.md +++ b/s2-site/docs/manual/basic/totals.zh.md @@ -5,7 +5,7 @@ order: 5 ## 简介 -小计总计属于表的透视功能,可以给行头和列头分别配置小计总计。 +小计总计属于表的透视功能,可以给行头和列头分别配置汇总能力,展示小计总计,开启 [自定义行列头](/manual/advanced/custom/custom-header) 时,对应行列头的汇总能力无效。 ### 小计 @@ -81,53 +81,7 @@ order: 5 配置 [S2Options](/docs/api/general/S2Options#total) 的 `totals` 属性来实现是否展示行列小计总计以及显示位置,类型如下: -#### Totals - -功能描述: 行/列小计总计配置 - -| 参数 | 说明 | 类型 | 默认值 | 必选 | -| ---- | ------ | --------------------------------------------- | ------ | ---- | -| row | 列总计 | [Total](/docs/api/general/S2Options#total) | - | | -| col | 行总计 | [Total](/docs/api/general/S2Options#total) | - | | - -#### Total - -功能描述:小计总计算配置 - -| 参数 | 说明 | 类型 | 默认值 | 必选 | -| ------------------- | ------------------------ | ------------ | ------ | ---- | -| showGrandTotals | 是否显示总计 | `boolean` | false | | -| showSubTotals | 是否显示小计。配置为对象时,`always` 用于控制当子维度小于 2 个时是否始终展示小计,默认展示 | `boolean \| { always: boolean }` | false | | -| subTotalsDimensions | 小计的汇总维度 | `string[]` | [] | | -| reverseGrandTotalsLayout | 总计布局位置,默认下或右 | `boolean` | false | | -| reverseSubTotalsLayout | 小计布局位置,默认下或右 | `boolean` | false | | -| grandTotalsLabel | 总计别名 | `string` | `总计` | | -| subTotalsLabel | 小计别名 | `string` | `小计` | | -| calcGrandTotals | 计算总计 | `CalcTotals` | | | -| calcSubTotals | 计算小计 | `CalcTotals` | | | - -```ts -const s2Options = { - totals: { - row: { - showGrandTotals: true, - showSubTotals: true, - reverseGrandTotalsLayout: true, - reverseSubTotalsLayout: true, - subTotalsDimensions: ['province'], - grandTotalsGroupDimensions: ['city'], - subTotalsGroupDimensions: ['type', 'sub_type'], - }, - col: { - showGrandTotals: true, - showSubTotals: true, - reverseGrandTotalsLayout: true, - reverseSubTotalsLayout: true, - subTotalsDimensions: ['type'], - }, - }, -}; -``` + ### 2. 数据 @@ -269,11 +223,12 @@ const s2Options = { 注意:`data` 为明细数据,如需获取包含汇总的数据 -```ts +```ts | pure import { QueryDataType } from '@antv/s2'; const calcFunc = (query, data, spreadsheet) => { - const allData = spreadsheet.dataSet.getMultiData(query, { + const allData = spreadsheet.dataSet.getCellMultiData({ + query, queryType: QueryDataType.All, }); diff --git a/s2-site/examples/analysis/totals/demo/custom.ts b/s2-site/examples/analysis/totals/demo/custom.ts index 1d014e0dbc..86b8686d7f 100644 --- a/s2-site/examples/analysis/totals/demo/custom.ts +++ b/s2-site/examples/analysis/totals/demo/custom.ts @@ -39,7 +39,8 @@ fetch('https://render.alipay.com/p/yuyan/180020010001215413/s2/basic.json') }; const calcFunc: CalcTotals['calcFunc'] = (query, data, spreadsheet) => { - const allData = spreadsheet.dataSet.getMultiData(query, { + const allData = spreadsheet.dataSet.getCellMultiData({ + query, queryType: QueryDataType.All, }); diff --git a/s2-site/examples/custom/custom-dataset/demo/custom-strategy-sheet-dataset.ts b/s2-site/examples/custom/custom-dataset/demo/custom-strategy-sheet-dataset.ts index bb3db61674..4e0480edb1 100644 --- a/s2-site/examples/custom/custom-dataset/demo/custom-strategy-sheet-dataset.ts +++ b/s2-site/examples/custom/custom-dataset/demo/custom-strategy-sheet-dataset.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ /* eslint-disable max-classes-per-file */ import { - CustomTreePivotDataSet, + CustomGridPivotDataSet, DataCell, EMPTY_EXTRA_FIELD_PLACEHOLDER, EXTRA_COLUMN_FIELD, @@ -18,7 +18,7 @@ import { } from '@antv/s2'; import { isEmpty, isObject, keys, size } from 'lodash'; -class CustomDataSet extends CustomTreePivotDataSet { +class CustomDataSet extends CustomGridPivotDataSet { // 自定义单个数据查询逻辑 getCellData(params: GetCellDataParams) { console.log('getCellData:', params);