diff --git a/packages/s2-core/CHANGELOG.md b/packages/s2-core/CHANGELOG.md index eaf7019a24..8e9027b7d8 100644 --- a/packages/s2-core/CHANGELOG.md +++ b/packages/s2-core/CHANGELOG.md @@ -1,4 +1,21 @@ -# [@antv/s2-v1.51.0-alpha.1](https://github.com/antvis/S2/compare/@antv/s2-v1.50.0...@antv/s2-v1.51.0-alpha.1) (2023-09-14) +# [@antv/s2-v1.51.1](https://github.com/antvis/S2/compare/@antv/s2-v1.51.0...@antv/s2-v1.51.1) (2023-10-13) + + +### Bug Fixes + +* **layout:** 修复隐藏结点时对父节点的布局计算错误 close [#2355](https://github.com/antvis/S2/issues/2355) ([#2360](https://github.com/antvis/S2/issues/2360)) ([c8d2c94](https://github.com/antvis/S2/commit/c8d2c94c21527ae52ece3485524d374d09b1cba7)) +* **table-sheet:** 明细表数据为空时错误的展示一行空数据 close [#2255](https://github.com/antvis/S2/issues/2255) ([#2357](https://github.com/antvis/S2/issues/2357)) ([7751c49](https://github.com/antvis/S2/commit/7751c4968b69752243928cbcd2c50333b06b2c66)) +* 列头绘制多列文本时错误的使用了数值单元格的样式 close [#2359](https://github.com/antvis/S2/issues/2359) ([#2364](https://github.com/antvis/S2/issues/2364)) ([4953b4e](https://github.com/antvis/S2/commit/4953b4e430f3c1857ce6648bcdc2493c37bb1092)) + +# [@antv/s2-v1.51.0](https://github.com/antvis/S2/compare/@antv/s2-v1.50.0...@antv/s2-v1.51.0) (2023-09-22) + + +### Features + +* 对比值无波动时也显示灰色 ([#2351](https://github.com/antvis/S2/issues/2351)) ([12f2d02](https://github.com/antvis/S2/commit/12f2d0268d447ec99a1227ffedd5ed266d93e86b)) +* 小计/总计功能,支持按维度分组汇总 ([#2346](https://github.com/antvis/S2/issues/2346)) ([20e608f](https://github.com/antvis/S2/commit/20e608f2447e9ffdb135d98e4cc7f39f1cfb308d)) + +# [@antv/s2-v1.50.0](https://github.com/antvis/S2/compare/@antv/s2-v1.49.2...@antv/s2-v1.50.0) (2023-09-09) ### Bug Fixes diff --git a/packages/s2-core/__tests__/bugs/issue-2359-spec.ts b/packages/s2-core/__tests__/bugs/issue-2359-spec.ts new file mode 100644 index 0000000000..04079ebc2a --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2359-spec.ts @@ -0,0 +1,140 @@ +/** + * @description spec for issue #2359 + * https://github.com/antvis/S2/issues/2359 + * 明细表: 自定义列头误用 dataCell 样式 + */ +import { pick } from 'lodash'; +import { createTableSheet } from 'tests/util/helpers'; +import { TableColCell, drawObjectText } from '../../src'; +import type { S2CellType, S2Options } from '@/common/interface'; + +class TestColCell extends TableColCell { + drawTextShape() { + drawObjectText(this, { + values: [['A', 'B', 'C']], + }); + } +} + +const s2Options: S2Options = { + width: 300, + height: 480, + showSeriesNumber: true, + colCell: (...args) => new TestColCell(...args), +}; + +describe('Table Sheet Custom Multiple Values Tests', () => { + test('should use current cell text theme', () => { + const s2 = createTableSheet(s2Options); + + s2.setTheme({ + colCell: { + measureText: { + fontSize: 12, + }, + bolderText: { + fontSize: 14, + }, + text: { + fontSize: 20, + fill: 'red', + }, + }, + dataCell: { + text: { + fontSize: 30, + fill: 'green', + }, + }, + }); + s2.render(); + + const mapTheme = (cell: S2CellType) => { + return cell + .getTextShapes() + .map((shape) => pick(shape.attr(), ['fill', 'fontSize'])); + }; + + const colCellTexts = s2 + .getColumnNodes() + .map((node) => mapTheme(node.belongsCell)); + + const dataCellTexts = s2.interaction + .getPanelGroupAllDataCells() + .map(mapTheme); + + expect(colCellTexts).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "fill": "red", + "fontSize": 20, + }, + Object { + "fill": "red", + "fontSize": 20, + }, + Object { + "fill": "red", + "fontSize": 20, + }, + ], + Array [ + Object { + "fill": "red", + "fontSize": 20, + }, + Object { + "fill": "red", + "fontSize": 20, + }, + Object { + "fill": "red", + "fontSize": 20, + }, + ], + ] + `); + + expect(dataCellTexts).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "fill": "#000000", + "fontSize": 12, + }, + ], + Array [ + Object { + "fill": "#000000", + "fontSize": 12, + }, + ], + Array [ + Object { + "fill": "#000000", + "fontSize": 12, + }, + ], + Array [ + Object { + "fill": "green", + "fontSize": 30, + }, + ], + Array [ + Object { + "fill": "green", + "fontSize": 30, + }, + ], + Array [ + Object { + "fill": "green", + "fontSize": 30, + }, + ], + ] + `); + }); +}); diff --git a/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts b/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts index b6c4a11dbd..7aecda2f1d 100644 --- a/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts @@ -347,6 +347,34 @@ describe('SpreadSheet Hidden Columns Tests', () => { expect(hiddenColumnsInfo).toBeTruthy(); expect(parentNode.hiddenChildNodeInfo).toEqual(hiddenColumnsInfo); }); + // https://github.com/antvis/S2/issues/2355 + test('should render correctly x and width after hide columns when there is only one value for the higher-level dimension.', () => { + const nodeId = 'root[&]笔[&]义乌[&]price'; + + pivotSheet.setOptions({ + style: { + colCfg: { + width: 100, + }, + }, + }); + const data = pivotSheet.dataCfg.data.map((i) => ({ ...i, cost: 0 })); + pivotSheet.setDataCfg({ + data, + fields: { + values: ['cost', 'price'], + }, + }); + pivotSheet.render(); + + pivotSheet.interaction.hideColumns([nodeId]); + const rootNode = pivotSheet + .getColumnNodes() + .find((node) => node.id === 'root[&]笔'); + + expect(rootNode.width).toEqual(300); + expect(rootNode.x).toEqual(0); + }); // https://github.com/antvis/S2/issues/2194 test('should render correctly when always hidden last column', () => { diff --git a/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts index 28c3082153..870c3947db 100644 --- a/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts @@ -92,6 +92,7 @@ describe('header cell formatter test', () => { label: '总计', isTotals: true, isGrandTotals: true, + isTotalRoot: true, }); const rowSubTotalNode = new Node({ diff --git a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-spec.ts b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-spec.ts index 2206b0f4b4..ab9f72ce28 100644 --- a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-spec.ts +++ b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-spec.ts @@ -158,6 +158,10 @@ describe('Pivot Dataset Test', () => { getDimensionsWithoutPathPre(sortedDimensionValues[EXTRA_FIELD]), ).toEqual(['number', 'number', 'number', 'number']); }); + + test('should get correctly empty dataset result', () => { + expect(dataSet.isEmpty()).toBeFalsy(); + }); }); describe('test for query data', () => { diff --git a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-total-spec.ts b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-total-spec.ts index f87a27ff43..2f4c623f95 100644 --- a/packages/s2-core/__tests__/unit/data-set/pivot-data-set-total-spec.ts +++ b/packages/s2-core/__tests__/unit/data-set/pivot-data-set-total-spec.ts @@ -5,10 +5,10 @@ import { get, keys } from 'lodash'; import * as multiDataCfg from 'tests/data/simple-data.json'; import { assembleDataCfg, TOTALS_OPTIONS } from '../../util'; import { EXTRA_FIELD, VALUE_FIELD } from '@/common/constant'; -import { type S2DataConfig, Aggregation } from '@/common/interface'; -import { PivotSheet } from '@/sheet-type'; -import { PivotDataSet } from '@/data-set/pivot-data-set'; +import { Aggregation, type S2DataConfig } from '@/common/interface'; import { Store } from '@/common/store'; +import { PivotDataSet } from '@/data-set/pivot-data-set'; +import { PivotSheet } from '@/sheet-type'; import { getDimensionsWithoutPathPre } from '@/utils/dataset/pivot-data-set'; jest.mock('@/sheet-type'); @@ -835,50 +835,50 @@ describe('Pivot Dataset Total Test', () => { }); test('should get correct boolean of dimensionValue is a query condition', () => { expect( - dataSet.checkAccordQueryWithDimensionValue( - '浙江省[&]杭州市[&]家具[&]桌子', - { + dataSet.checkAccordQueryWithDimensionValue({ + dimensionValues: '浙江省[&]杭州市[&]家具[&]桌子', + query: { province: '浙江省', city: 'A', type: 'Abc', }, - dataCfg.fields.rows, - 'province', - ), + dimensions: dataCfg.fields.rows, + field: 'province', + }), ).toEqual(true); expect( - dataSet.checkAccordQueryWithDimensionValue( - '浙江省[&]杭州市[&]家具[&]桌子', - { + dataSet.checkAccordQueryWithDimensionValue({ + dimensionValues: '浙江省[&]杭州市[&]家具[&]桌子', + query: { province: '浙江省', city: '杭州市', type: '家具', }, - dataCfg.fields.rows, - 'sub_type', - ), + dimensions: dataCfg.fields.rows, + field: 'sub_type', + }), ).toEqual(true); expect( - dataSet.checkAccordQueryWithDimensionValue( - '浙江省[&]杭州市[&]家具[&]桌子', - { + dataSet.checkAccordQueryWithDimensionValue({ + dimensionValues: '浙江省[&]杭州市[&]家具[&]桌子', + query: { province: '浙江省', city: '不是杭州市', type: '家具', }, - dataCfg.fields.rows, - 'sub_type', - ), + dimensions: dataCfg.fields.rows, + field: 'sub_type', + }), ).toEqual(false); expect( - dataSet.checkAccordQueryWithDimensionValue( - '浙江省[&]杭州市[&]家具[&]桌子', - { + dataSet.checkAccordQueryWithDimensionValue({ + dimensionValues: '浙江省[&]杭州市[&]家具[&]桌子', + query: { province: '浙江省', }, - dataCfg.fields.rows, - 'sub_type', - ), + dimensions: dataCfg.fields.rows, + field: 'sub_type', + }), ).toEqual(true); }); test('get correct query list when query need to be processed', () => { diff --git a/packages/s2-core/__tests__/unit/data-set/table-data-set-spec.ts b/packages/s2-core/__tests__/unit/data-set/table-data-set-spec.ts index 4da12e138b..8e990596c3 100644 --- a/packages/s2-core/__tests__/unit/data-set/table-data-set-spec.ts +++ b/packages/s2-core/__tests__/unit/data-set/table-data-set-spec.ts @@ -99,6 +99,10 @@ describe('Table Mode Dataset Test', () => { test('should get correct meta data', () => { expect(dataSet.meta).toEqual(expect.objectContaining([])); }); + + test('should get correctly empty dataset result', () => { + expect(dataSet.isEmpty()).toBeFalsy(); + }); }); describe('test for query data', () => { diff --git a/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts b/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts index 428e07a9fa..ad72e6226a 100644 --- a/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts @@ -13,9 +13,11 @@ import { DataCell, DEFAULT_STYLE, type Fields, Node } from '@/index'; import { getFrozenLeafNodesCount } from '@/facet/utils'; import { SpreadSheet } from '@/sheet-type'; import { getTheme } from '@/theme'; + const actualDataSet = jest.requireActual( '@/data-set/base-data-set', ).BaseDataSet; + jest.mock('@/sheet-type', () => { const container = new Canvas({ width: 100, @@ -59,6 +61,9 @@ jest.mock('@/sheet-type', () => { interaction: { clearHoverTimer: jest.fn(), }, + dataSet: { + isEmpty: jest.fn(), + }, }; }), }; @@ -77,6 +82,7 @@ jest.mock('@/data-set/table-data-set', () => { getCellData: () => 1, getFieldMeta: jest.fn(), getFieldFormatter: actualDataSet.prototype.getFieldFormatter, + isEmpty: jest.fn(), }; }), }; diff --git a/packages/s2-core/__tests__/unit/utils/__snapshots__/sort-action-spec.tsx.snap b/packages/s2-core/__tests__/unit/utils/__snapshots__/sort-action-spec.tsx.snap new file mode 100644 index 0000000000..a27042ad8a --- /dev/null +++ b/packages/s2-core/__tests__/unit/utils/__snapshots__/sort-action-spec.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GetSortByMeasureValues Total Fallback Tests should sort by col total whit group 1`] = ` +Array [ + Object { + "$$extra$$": "price", + "$$value$$": "666", + "city": "杭州", + "price": "666", + "type": "笔", + }, + Object { + "$$extra$$": "price", + "$$value$$": "999", + "city": "杭州", + "price": "999", + "type": "纸张", + }, +] +`; diff --git a/packages/s2-core/__tests__/unit/utils/export/export-spec.ts b/packages/s2-core/__tests__/unit/utils/export/export-spec.ts index 8e763350f2..d160c70afe 100644 --- a/packages/s2-core/__tests__/unit/utils/export/export-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/export-spec.ts @@ -255,8 +255,8 @@ describe('PivotSheet Export Test', () => { subTotalsDimensions: ['province'], }, col: { - totalsDimensionsGroup: ['city', 'type'], - subTotalsDimensionsGroup: ['sub_type'], + totalsGroupDimensions: ['city', 'type'], + subTotalsGroupDimensions: ['sub_type'], showGrandTotals: true, showSubTotals: true, subTotalsDimensions: ['type'], @@ -291,8 +291,8 @@ describe('PivotSheet Export Test', () => { subTotalsDimensions: ['province'], }, col: { - totalsDimensionsGroup: ['city', 'sub_type', 'province'], - subTotalsDimensionsGroup: ['sub_type'], + totalsGroupDimensions: ['city', 'sub_type', 'province'], + subTotalsGroupDimensions: ['sub_type'], showGrandTotals: true, showSubTotals: true, subTotalsDimensions: ['type'], diff --git a/packages/s2-core/__tests__/unit/utils/sort-action-spec.tsx b/packages/s2-core/__tests__/unit/utils/sort-action-spec.tsx index 5b90bdb267..e9989b8847 100644 --- a/packages/s2-core/__tests__/unit/utils/sort-action-spec.tsx +++ b/packages/s2-core/__tests__/unit/utils/sort-action-spec.tsx @@ -714,4 +714,56 @@ describe('GetSortByMeasureValues Total Fallback Tests', () => { }, ]); }); + + test('should sort by col total whit group', () => { + const currentOptions = { + totals: { + col: { + totalsGroupDimensions: ['city'], + showGrandTotals: true, + }, + }, + } as S2Options; + + const dataConfig = { + ...sortData, + data: [ + ...sortData.data, + { + city: '杭州', + type: '纸张', + price: '999', + }, + { + city: '杭州', + type: '笔', + price: '666', + }, + ], + fields: { + rows: ['type'], + columns: ['province', 'city'], + values: ['price'], + }, + }; + sheet = new PivotSheet(getContainer(), dataConfig, currentOptions); + sheet.render(); + // 根据列(类别)的总和排序 + const sortParam: SortParam = { + sortFieldId: 'type', + sortByMeasure: TOTAL_VALUE, + sortMethod: 'desc', + query: { + [EXTRA_FIELD]: 'price', + city: '杭州', + }, + }; + + const params: SortActionParams = { + dataSet: sheet.dataSet, + sortParam, + }; + const measureValues = getSortByMeasureValues(params); + expect(measureValues).toMatchSnapshot(); + }); }); diff --git a/packages/s2-core/package.json b/packages/s2-core/package.json index 0fe6a13474..cbc465052a 100644 --- a/packages/s2-core/package.json +++ b/packages/s2-core/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "@antv/s2", - "version": "1.51.0-alpha.1", + "version": "1.51.1", "main": "lib/index.js", "unpkg": "dist/index.min.js", "module": "esm/index.js", diff --git a/packages/s2-core/src/cell/table-col-cell.ts b/packages/s2-core/src/cell/table-col-cell.ts index 3368f9ffd6..be1c6a04c4 100644 --- a/packages/s2-core/src/cell/table-col-cell.ts +++ b/packages/s2-core/src/cell/table-col-cell.ts @@ -77,6 +77,7 @@ export class TableColCell extends ColCell { y, }; } + return { x: position.x + x - scrollX, y: position.y + y, diff --git a/packages/s2-core/src/common/interface/basic.ts b/packages/s2-core/src/common/interface/basic.ts index 94b7b22767..5770ce51fd 100644 --- a/packages/s2-core/src/common/interface/basic.ts +++ b/packages/s2-core/src/common/interface/basic.ts @@ -1,4 +1,5 @@ import type { Event, ShapeAttrs } from '@antv/g-canvas'; +import type { MergedCell } from '../../cell'; import type { CellTypes } from '../../common/constant'; import type { CustomTreeItem, Data, ResizeInfo } from '../../common/interface'; import type { FrameConfig } from '../../common/interface/frame'; @@ -12,7 +13,6 @@ import type { BaseHeaderConfig } from '../../facet/header/base'; import type { Hierarchy } from '../../facet/layout/hierarchy'; import type { Node } from '../../facet/layout/node'; import type { SpreadSheet } from '../../sheet-type'; -import type { MergedCell } from '../../cell'; import type { S2CellType } from './interaction'; import type { DataItem } from './s2DataConfig'; @@ -152,9 +152,9 @@ export interface Total { // sub label's display name, default = '小计' subLabel?: string; /** 总计分组维度 */ - totalsDimensionsGroup?: string[]; + totalsGroupDimensions?: string[]; /** 小计分组维度 */ - subTotalsDimensionsGroup?: string[]; + subTotalsGroupDimensions?: string[]; } /** diff --git a/packages/s2-core/src/data-set/base-data-set.ts b/packages/s2-core/src/data-set/base-data-set.ts index 1c95f5e736..3697f895e3 100644 --- a/packages/s2-core/src/data-set/base-data-set.ts +++ b/packages/s2-core/src/data-set/base-data-set.ts @@ -3,6 +3,7 @@ import { find, get, identity, + isEmpty, isNil, map, max, @@ -117,6 +118,10 @@ export abstract class BaseDataSet { return this.displayData; } + public isEmpty() { + return isEmpty(this.getDisplayDataSet()); + } + public getValueRangeByField(field: string): ValueRange { const cacheRange = getValueRangeState(this.spreadsheet, field); if (cacheRange) { @@ -201,12 +206,14 @@ export abstract class BaseDataSet { * @param isTotals * @param isRow * @param drillDownFields + * @param includeTotalData 用于标记是否包含汇总数据,例如在排序功能中需要汇总数据,在计算汇总值中只取明细数据 */ public abstract getMultiData( query: DataType, isTotals?: boolean, isRow?: boolean, drillDownFields?: string[], + includeTotalData?: boolean, ): DataType[]; public moreThanOneValue() { diff --git a/packages/s2-core/src/data-set/interface.ts b/packages/s2-core/src/data-set/interface.ts index 3d250ecad5..672766c8ef 100644 --- a/packages/s2-core/src/data-set/interface.ts +++ b/packages/s2-core/src/data-set/interface.ts @@ -51,6 +51,15 @@ export interface CellDataParams { totalStatus?: TotalStatus; } +export interface CheckAccordQueryParams { + // item of sortedDimensionValues,es: "浙江省[&]杭州市[&]家具[&]桌子" + dimensionValues: string; + query: DataType; + // rows or columns + dimensions: string[]; + field: string; +} + export interface TotalStatus { isRowTotal: boolean; isRowSubTotal: boolean; 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 25c37d07b5..792afbaa4c 100644 --- a/packages/s2-core/src/data-set/pivot-data-set.ts +++ b/packages/s2-core/src/data-set/pivot-data-set.ts @@ -15,10 +15,12 @@ import { isNumber, isUndefined, keys, + some, uniq, unset, values, } from 'lodash'; +import type { CellMeta } from '../common'; import { EXTRA_FIELD, ID_SEPARATOR, @@ -33,9 +35,9 @@ import type { Meta, PartDrillDownDataCache, PartDrillDownFieldInLevel, + RowData, S2DataConfig, ViewMeta, - RowData, } from '../common/interface'; import { Node } from '../facet/layout/node'; import { @@ -58,10 +60,10 @@ import { } from '../utils/dataset/pivot-data-set'; import { calcActionByType } from '../utils/number-calculate'; import { handleSortAction } from '../utils/sort-action'; -import type { CellMeta } from '../common'; import { BaseDataSet } from './base-data-set'; import type { CellDataParams, + CheckAccordQueryParams, DataType, PivotMeta, SortedDimensionValues, @@ -320,26 +322,30 @@ export class PivotDataSet extends BaseDataSet { }; } - // rows :['province','city','type'] - // query: ['浙江省',undefined] => return: ['文具','家具'] - public getTotalDimensionValues(field: string, query?: DataType): string[] { + public getDimensionsByField(field: string): string[] { const { rows = [], columns = [] } = this.fields || {}; - let dimensions: string[] = []; if (includes(rows, field)) { - dimensions = rows; - } else if (includes(columns, field)) { - dimensions = columns as string[]; + return rows; + } + if (includes(columns, field)) { + return columns as string[]; } - let allCurrentFieldDimensionValues = - this.sortedDimensionValues[field] || []; - allCurrentFieldDimensionValues = allCurrentFieldDimensionValues.filter( - (dimValue) => - this.checkAccordQueryWithDimensionValue( - dimValue, - query, - dimensions, - field, - ), + return []; + } + + // rows :['province','city','type'] + // query: ['浙江省',undefined] => return: ['文具','家具'] + public getTotalDimensionValues(field: string, query?: DataType): string[] { + const dimensions = this.getDimensionsByField(field); + const allCurrentFieldDimensionValues = ( + this.sortedDimensionValues[field] || [] + ).filter((dimValue) => + this.checkAccordQueryWithDimensionValue({ + dimensionValues: dimValue, + query, + dimensions, + field, + }), ); return filterUndefined( uniq(getDimensionsWithoutPathPre([...allCurrentFieldDimensionValues])), @@ -396,12 +402,7 @@ export class PivotDataSet extends BaseDataSet { } getTotalValue(query: DataType, totalStatus?: TotalStatus) { - let effectiveStatus = false; - forEach(totalStatus, (bol) => { - if (bol) { - effectiveStatus = true; - } - }); + const effectiveStatus = some(totalStatus); const status = effectiveStatus ? totalStatus : this.getTotalStatus(query); const { aggregation, calcFunc } = getAggregationAndCalcFuncByQuery( @@ -544,15 +545,11 @@ export class PivotDataSet extends BaseDataSet { * dimensions = ['province','city'] * query = [province: '杭州市', type: '文具'] * field = 'sub_type' - * 浙江省[&]杭州市[&]家具[&]桌子 => true - * 四川省[&]成都市[&]文具[&]笔 => false + * DimensionValue: 浙江省[&]杭州市[&]家具[&]桌子 => true + * DimensionValue: 四川省[&]成都市[&]文具[&]笔 => false */ - checkAccordQueryWithDimensionValue( - dimensionValues: string, - query, - dimensions: string[], - field: string, - ): boolean { + checkAccordQueryWithDimensionValue(params: CheckAccordQueryParams): boolean { + const { dimensionValues, query, dimensions, field } = params; for (const [index, dimension] of dimensions.entries()) { const queryValue = get(query, dimension); if (queryValue) { @@ -577,12 +574,12 @@ export class PivotDataSet extends BaseDataSet { * {'百事公司','可乐','undefined','price'}, * ] */ - getTotalGroupQueries(dimensions: string[], query) { - let queryArray = [query]; + getTotalGroupQueries(dimensions: string[], originQuery: DataType) { + let queries = [originQuery]; let existDimensionGroupKey = null; - for (let i = dimensions.length; i > 0; i--) { - const key = dimensions[i - 1]; - if (keys(query).includes(key)) { + for (let i = dimensions.length - 1; i >= 0; i--) { + const key = dimensions[i]; + if (keys(originQuery).includes(key)) { if (key !== EXTRA_FIELD) { existDimensionGroupKey = key; } @@ -590,63 +587,65 @@ export class PivotDataSet extends BaseDataSet { const allCurrentFieldDimensionValues = this.sortedDimensionValues[existDimensionGroupKey]; let res = []; - const arrayLength = - allCurrentFieldDimensionValues[0].split(ID_SEPARATOR).length; - for (const queryItem of queryArray) { + for (const query of queries) { const resKeys = []; for (const dimValue of allCurrentFieldDimensionValues) { if ( - this.checkAccordQueryWithDimensionValue( - dimValue, - queryItem, + this.checkAccordQueryWithDimensionValue({ + dimensionValues: dimValue, + query, dimensions, - existDimensionGroupKey, - ) + field: existDimensionGroupKey, + }) ) { const arrTypeValue = dimValue.split(ID_SEPARATOR); - const currentKey = arrTypeValue[arrayLength - 2]; + const currentKey = arrTypeValue[i]; if (currentKey !== 'undefined') { resKeys.push(currentKey); } } } const queryList = uniq(resKeys).map((v) => { - return { ...queryItem, [key]: v }; + return { ...query, [key]: v }; }); res = concat(res, queryList); } - queryArray = res; + queries = res; existDimensionGroupKey = key; } } - return queryArray; + return queries; } // 有中间维度汇总的分组场景,将有中间 undefined 值的 query 处理为一组合法 query 后查询数据再合并 - private getGroupTotalMultiData(totalRows, rows, columns, query): DataType[] { + private getGroupTotalMultiData( + totalRows: string[], + originQuery: DataType, + ): DataType[] { + const { rows, columns } = this.fields; let result = []; - const rowTotalGroupQueries = this.getTotalGroupQueries(totalRows, query); + const rowTotalGroupQueries = this.getTotalGroupQueries( + totalRows, + originQuery, + ); let totalGroupQueries = []; - for (const queryItem of rowTotalGroupQueries) { + for (const query of rowTotalGroupQueries) { totalGroupQueries = concat( totalGroupQueries, - this.getTotalGroupQueries(columns as string[], queryItem), + this.getTotalGroupQueries(columns as string[], query), ); } - for (const queryItem of totalGroupQueries) { - const rowDimensionValues = getQueryDimValues(totalRows, queryItem); - const colDimensionValues = getQueryDimValues( - columns as string[], - queryItem, - ); + for (const query of totalGroupQueries) { + const rowDimensionValues = getQueryDimValues(totalRows, query); + const colDimensionValues = getQueryDimValues(columns as string[], query); const path = getDataPath({ rowDimensionValues, colDimensionValues, careUndefined: true, isFirstCreate: true, rowFields: rows, - colFields: columns, + colFields: columns as string[], rowPivotMeta: this.rowPivotMeta, colPivotMeta: this.colPivotMeta, }); @@ -661,6 +660,7 @@ export class PivotDataSet extends BaseDataSet { isTotals?: boolean, isRow?: boolean, drillDownFields?: string[], + includeTotalData?: boolean, ): DataType[] { if (isEmpty(query)) { return compact(customFlattenDeep(this.indexesData)); @@ -671,10 +671,11 @@ export class PivotDataSet extends BaseDataSet { : rows; // existDimensionGroup:当 undefined 维度后面有非 undefined,为维度分组场景,将非 undefined 维度前的维度填充为所有可能的维度值。 // 如 [undefined , '杭州市' , undefined , 'number'] - const existDimensionGroup = this.checkExistDimensionGroup(query); + const existDimensionGroup = + !includeTotalData && this.checkExistDimensionGroup(query); let result = []; if (existDimensionGroup) { - result = this.getGroupTotalMultiData(totalRows, rows, columns, query); + result = this.getGroupTotalMultiData(totalRows, query); } else { const rowDimensionValues = getQueryDimValues(totalRows, query); const colDimensionValues = getQueryDimValues(columns as string[], query); 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 278385b561..a36b141801 100644 --- a/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts +++ b/packages/s2-core/src/facet/layout/build-gird-hierarchy.ts @@ -3,21 +3,15 @@ import { EXTRA_FIELD } from '../../common/constant'; import { addTotals } from '../../utils/layout/add-totals'; import { generateHeaderNodes } from '../../utils/layout/generate-header-nodes'; import { getDimsCondition } from '../../utils/layout/get-dims-condition-by-node'; +import { whetherLeafByLevel } from '../../utils/layout/whether-leaf-by-level'; import type { FieldValue, GridHeaderParams } from '../layout/interface'; import { layoutArrange } from '../layout/layout-hooks'; import { TotalMeasure } from '../layout/total-measure'; -import { whetherLeafByLevel } from '../../utils/layout/whether-leaf-by-level'; import { TotalClass } from './total-class'; -/** - * Build grid hierarchy in rows or columns - * - * @param params - */ -export const buildGridHierarchy = (params: GridHeaderParams) => { +const buildTotalGridHierarchy = (params: GridHeaderParams) => { const { addTotalMeasureInTotal, - addMeasureInTotalQuery, parentNode, currentField, fields, @@ -32,88 +26,115 @@ export const buildGridHierarchy = (params: GridHeaderParams) => { const fieldName = dataSet.getFieldName(currentField); let query = {}; - if (parentNode.isTotals) { - const totalsConfig = spreadsheet.getTotalsConfig(currentField); - const dimensionGroup = parentNode.isGrandTotals - ? totalsConfig.totalsDimensionsGroup - : totalsConfig.subTotalsDimensionsGroup; - if (dimensionGroup?.includes(currentField)) { - query = getDimsCondition(parentNode); - const dimValues = dataSet.getTotalDimensionValues(currentField, query); - fieldValues.push( - ...(dimValues || []).map( - (value) => - new TotalClass( - value, - parentNode.isSubTotals, - parentNode.isGrandTotals, - false, - ), - ), - ); - if (isEmpty(fieldValues)) { - fieldValues.push(fieldName); - } - } else if (addTotalMeasureInTotal && currentField === EXTRA_FIELD) { - // add total measures - query = getDimsCondition(parentNode); - fieldValues.push(...values.map((v) => new TotalMeasure(v))); - } else if (whetherLeafByLevel({ facetCfg, level: index, fields })) { - // 如果最后一级没有分组维度,则将上一个结点设为叶子结点 - parentNode.isLeaf = true; - hierarchy.pushIndexNode(parentNode); - parentNode.rowIndex = hierarchy.getIndexNodes().length - 1; - return; - } else { - // 如果是空维度,则跳转到下一级 level - buildGridHierarchy({ ...params, currentField: fields[index + 1] }); - return; + const totalsConfig = spreadsheet.getTotalsConfig(currentField); + const dimensionGroup = parentNode.isGrandTotals + ? totalsConfig.totalsGroupDimensions + : totalsConfig.subTotalsGroupDimensions; + if (dimensionGroup?.includes(currentField)) { + query = getDimsCondition(parentNode); + const dimValues = dataSet.getTotalDimensionValues(currentField, query); + fieldValues.push( + ...(dimValues || []).map( + (value) => + new TotalClass({ + label: value, + isSubTotals: parentNode.isSubTotals, + isGrandTotals: parentNode.isGrandTotals, + isTotalRoot: false, + }), + ), + ); + if (isEmpty(fieldValues)) { + fieldValues.push(fieldName); } + } else if (addTotalMeasureInTotal && currentField === EXTRA_FIELD) { + // add total measures + query = getDimsCondition(parentNode); + fieldValues.push(...values.map((v) => new TotalMeasure(v))); + } else if (whetherLeafByLevel({ facetCfg, level: index, fields })) { + // 如果最后一级没有分组维度,则将上一个结点设为叶子结点 + parentNode.isLeaf = true; + hierarchy.pushIndexNode(parentNode); + parentNode.rowIndex = hierarchy.getIndexNodes().length - 1; + return; } else { - // field(dimension)'s all values - query = getDimsCondition(parentNode, true); + // 如果是空维度,则跳转到下一级 level + buildTotalGridHierarchy({ ...params, currentField: fields[index + 1] }); + return; + } - const dimValues = dataSet.getDimensionValues(currentField, query); + const displayFieldValues = fieldValues.filter((value) => !isUndefined(value)); + generateHeaderNodes({ + ...params, + fieldValues: displayFieldValues, + level: index, + parentNode, + query, + }); +}; - const arrangedValues = layoutArrange( - dimValues, - facetCfg, - parentNode, - currentField, - ); - fieldValues.push(...(arrangedValues || [])); +const buildNormalGridHierarchy = (params: GridHeaderParams) => { + const { parentNode, currentField, fields, facetCfg } = params; + + const index = fields.indexOf(currentField); - // add skeleton for empty data + const { dataSet, spreadsheet } = facetCfg; + const fieldValues: FieldValue[] = []; + const fieldName = dataSet.getFieldName(currentField); - if (isEmpty(fieldValues)) { - if (currentField === EXTRA_FIELD) { - fieldValues.push(...dataSet.fields?.values); - } else { - fieldValues.push(fieldName); - } - } + let query = {}; + + // field(dimension)'s all values + query = getDimsCondition(parentNode, true); - // add totals if needed - addTotals({ - currentField, - lastField: fields[index - 1], - isFirstField: index === 0, - fieldValues, - spreadsheet, - }); + const dimValues = dataSet.getDimensionValues(currentField, query); + + const arrangedValues = layoutArrange( + dimValues, + facetCfg, + parentNode, + currentField, + ); + fieldValues.push(...(arrangedValues || [])); + + // add skeleton for empty data + + if (isEmpty(fieldValues)) { + if (currentField === EXTRA_FIELD) { + fieldValues.push(...dataSet.fields?.values); + } else { + fieldValues.push(fieldName); + } } + // add totals if needed + addTotals({ + currentField, + lastField: fields[index - 1], + isFirstField: index === 0, + fieldValues, + spreadsheet, + }); + const displayFieldValues = fieldValues.filter((value) => !isUndefined(value)); generateHeaderNodes({ - currentField, - fields, + ...params, fieldValues: displayFieldValues, - facetCfg, - hierarchy, - parentNode, level: index, + parentNode, query, - addMeasureInTotalQuery, - addTotalMeasureInTotal, }); }; + +/** + * Build grid hierarchy in rows or columns + * + * @param params + */ +export const buildGridHierarchy = (params: GridHeaderParams) => { + if (params.parentNode.isTotals) { + buildTotalGridHierarchy(params); + } else { + buildNormalGridHierarchy(params); + } +}; diff --git a/packages/s2-core/src/facet/layout/build-row-tree-hierarchy.ts b/packages/s2-core/src/facet/layout/build-row-tree-hierarchy.ts index 05afda5ffe..28697f9c11 100644 --- a/packages/s2-core/src/facet/layout/build-row-tree-hierarchy.ts +++ b/packages/s2-core/src/facet/layout/build-row-tree-hierarchy.ts @@ -20,7 +20,13 @@ const addTotals = ( // TODO valueInCol = false and one or more values if (totalsConfig.showGrandTotals) { const func = totalsConfig.reverseLayout ? 'unshift' : 'push'; - fieldValues[func](new TotalClass(totalsConfig.label, false, true)); + fieldValues[func]( + new TotalClass({ + label: totalsConfig.label, + isSubTotals: false, + isGrandTotals: true, + }), + ); } }; diff --git a/packages/s2-core/src/facet/layout/interface.ts b/packages/s2-core/src/facet/layout/interface.ts index 45cae6244e..3e15a02f5d 100644 --- a/packages/s2-core/src/facet/layout/interface.ts +++ b/packages/s2-core/src/facet/layout/interface.ts @@ -42,15 +42,8 @@ export interface TotalParams { spreadsheet: SpreadSheet; } -export interface HeaderNodesParams { - currentField: string; - fields: string[]; +export interface HeaderNodesParams extends GridHeaderParams { fieldValues: FieldValue[]; - addTotalMeasureInTotal: boolean; - addMeasureInTotalQuery: boolean; - facetCfg: SpreadSheetFacetCfg; - hierarchy: Hierarchy; - parentNode: Node; level: number; query: Record; } diff --git a/packages/s2-core/src/facet/layout/node.ts b/packages/s2-core/src/facet/layout/node.ts index cfc09d5466..e9e7ee0c0f 100644 --- a/packages/s2-core/src/facet/layout/node.ts +++ b/packages/s2-core/src/facet/layout/node.ts @@ -306,6 +306,9 @@ export class Node { public isTotalRoot?: boolean; + /** + * @deprecated 已废弃, 该属性只记录相邻一级的隐藏信息,将会在未来版本中移除 + */ public hiddenChildNodeInfo?: HiddenColumnsInfo | null; public extra?: Record; diff --git a/packages/s2-core/src/facet/layout/total-class.ts b/packages/s2-core/src/facet/layout/total-class.ts index 75f3e40ef2..36f47c995d 100644 --- a/packages/s2-core/src/facet/layout/total-class.ts +++ b/packages/s2-core/src/facet/layout/total-class.ts @@ -1,6 +1,16 @@ /** * Class to mark '小计' & '总计' */ + +export interface TotalClassConfig { + label: string; + // 是否属于小计汇总格 + isSubTotals: boolean; + // 是否属于总计汇总格 + isGrandTotals: boolean; + // 是否是”小计“、”总计“单元格本身 + isTotalRoot?: boolean; +} export class TotalClass { public label: string; @@ -8,15 +18,11 @@ export class TotalClass { public isGrandTotals: boolean; - // 是否为 小计 总计 根结点,即value = “小计”,单元格,此类结点不参与 query + // 是否为 小计/总计 根结点,即 value = “小计”,单元格,此类结点不参与 query public isTotalRoot: boolean; - public constructor( - label: string, - isSubTotals = false, - isGrandTotals = false, - isTotalRoot = false, - ) { + public constructor(params: TotalClassConfig) { + const { label, isSubTotals, isGrandTotals, isTotalRoot } = params; this.label = label; this.isSubTotals = isSubTotals; this.isGrandTotals = isGrandTotals; diff --git a/packages/s2-core/src/facet/pivot-facet.ts b/packages/s2-core/src/facet/pivot-facet.ts index bfab95c974..5be0b45732 100644 --- a/packages/s2-core/src/facet/pivot-facet.ts +++ b/packages/s2-core/src/facet/pivot-facet.ts @@ -26,9 +26,9 @@ import { DebuggerUtil } from '../common/debug'; import type { LayoutResult, ViewMeta } from '../common/interface'; import { getDataCellId, handleDataItem } from '../utils/cell/data-cell'; import { getActionIconConfig } from '../utils/cell/header-cell'; +import { getHeaderTotalStatus } from '../utils/dataset/pivot-data-set'; import { getIndexRangeWithOffsets } from '../utils/facet'; import { getCellWidth, safeJsonParse } from '../utils/text'; -import { getTotalStatusByRowCol } from '../utils/dataset/pivot-data-set'; import { BaseFacet } from './base-facet'; import { buildHeaderHierarchy } from './layout/build-header-hierarchy'; import type { Hierarchy } from './layout/hierarchy'; @@ -95,7 +95,7 @@ export class PivotFacet extends BaseFacet { } : {}; const dataQuery = merge({}, rowQuery, colQuery, measureInfo); - const totalStatus = getTotalStatusByRowCol(row, col); + const totalStatus = getHeaderTotalStatus(row, col); const data = dataSet.getCellData({ query: dataQuery, rowNode: row, @@ -252,9 +252,9 @@ export class PivotFacet extends BaseFacet { leafNodes.push(parentNode); const firstVisibleChildNode = parentNode.children?.find( - (childNode) => !childNode.hiddenChildNodeInfo, + (childNode) => childNode.width, ); - // 父节点 x 坐标 = 第一个未隐藏的子节点的 x 坐标 + // 父节点 x 坐标 = 第一个正常布局处理过的子节点 x 坐标(width 有值认为是正常布局过) const parentNodeX = firstVisibleChildNode?.x; // 父节点宽度 = 所有子节点宽度之和 const parentNodeWidth = sumBy(parentNode.children, 'width'); @@ -323,7 +323,7 @@ export class PivotFacet extends BaseFacet { col.isTotalMeasure || rowNode.isTotals || rowNode.isTotalMeasure, - totalStatus: getTotalStatusByRowCol(rowNode, col), + totalStatus: getHeaderTotalStatus(rowNode, col), }); if (cellData) { @@ -551,8 +551,8 @@ export class PivotFacet extends BaseFacet { const fields = isRowHeader ? rows : columns; const totalConfig = isRowHeader ? totals.row : totals.col; const dimensionGroup = isSubTotal - ? totalConfig.subTotalsDimensionsGroup || [] - : totalConfig.totalsDimensionsGroup || []; + ? totalConfig.subTotalsGroupDimensions || [] + : totalConfig.totalsGroupDimensions || []; const multipleMap: number[] = Array.from({ length: maxLevel + 1 }, () => 1); for (let level = maxLevel; level > 0; level--) { const currentField = fields[level] as string; @@ -679,7 +679,7 @@ export class PivotFacet extends BaseFacet { col.isTotalMeasure || rowNode.isTotals || rowNode.isTotalMeasure, - totalStatus: getTotalStatusByRowCol(rowNode, col), + totalStatus: getHeaderTotalStatus(rowNode, col), }); const cellDataKeys = keys(cellData); diff --git a/packages/s2-core/src/facet/table-facet.ts b/packages/s2-core/src/facet/table-facet.ts index 0cd0f14ed2..e1608eb241 100644 --- a/packages/s2-core/src/facet/table-facet.ts +++ b/packages/s2-core/src/facet/table-facet.ts @@ -43,7 +43,7 @@ import { getFrozenRowsForGrid, getRowsForGrid, } from '../utils/grid'; -import type { PanelIndexes } from '../utils/indexes'; +import type { Indexes, PanelIndexes } from '../utils/indexes'; import { getValidFrozenOptions } from '../utils/layout/frozen'; import { BaseFacet } from './base-facet'; import { CornerBBox } from './bbox/cornerBBox'; @@ -1129,14 +1129,17 @@ export class TableFacet extends BaseFacet { } } - const indexes = calculateInViewIndexes( - scrollX, - scrollY, - this.viewCellWidths, - this.viewCellHeights, - finalViewport, - this.getRealScrollX(this.cornerBBox.width), - ); + // https://github.com/antvis/S2/issues/2255 + const indexes = this.spreadsheet.dataSet.isEmpty() + ? ([] as unknown as Indexes) + : calculateInViewIndexes( + scrollX, + scrollY, + this.viewCellWidths, + this.viewCellHeights, + finalViewport, + this.getRealScrollX(this.cornerBBox.width), + ); this.panelScrollGroupIndexes = indexes; diff --git a/packages/s2-core/src/sheet-type/spread-sheet.ts b/packages/s2-core/src/sheet-type/spread-sheet.ts index b5470a4fd6..f3d48faad9 100644 --- a/packages/s2-core/src/sheet-type/spread-sheet.ts +++ b/packages/s2-core/src/sheet-type/spread-sheet.ts @@ -618,8 +618,8 @@ export abstract class SpreadSheet extends EE { return { label: i18n('总计'), subLabel: i18n('小计'), - totalsDimensionsGroup: [], - subTotalsDimensionsGroup: [], + totalsGroupDimensions: [], + subTotalsGroupDimensions: [], ...totalConfig, showSubTotals, }; diff --git a/packages/s2-core/src/sheet-type/table-sheet.ts b/packages/s2-core/src/sheet-type/table-sheet.ts index 3612f9d960..21f372f269 100644 --- a/packages/s2-core/src/sheet-type/table-sheet.ts +++ b/packages/s2-core/src/sheet-type/table-sheet.ts @@ -123,6 +123,7 @@ export class TableSheet extends SpreadSheet { } return new TableDataCell(facet, this); }; + return { ...this.options, ...fields, 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 553f74ae53..c11b0ebb34 100644 --- a/packages/s2-core/src/utils/dataset/pivot-data-set.ts +++ b/packages/s2-core/src/utils/dataset/pivot-data-set.ts @@ -9,6 +9,7 @@ import { set, } from 'lodash'; import { EXTRA_FIELD, ID_SEPARATOR, ROOT_ID } from '../../common/constant'; +import type { Meta } from '../../common/interface/basic'; import type { DataPathParams, DataType, @@ -16,7 +17,6 @@ import type { SortedDimensionValues, TotalStatus, } from '../../data-set/interface'; -import type { Meta } from '../../common/interface/basic'; import type { Node } from '../../facet/layout/node'; interface Param { @@ -344,7 +344,7 @@ export function generateExtraFieldMeta( return extraFieldMeta; } -export function getTotalStatusByRowCol(row: Node, col: Node): TotalStatus { +export function getHeaderTotalStatus(row: Node, col: Node): TotalStatus { return { isRowTotal: row.isGrandTotals, isRowSubTotal: row.isSubTotals, diff --git a/packages/s2-core/src/utils/export/copy.ts b/packages/s2-core/src/utils/export/copy.ts index f78c7e3678..4802769f99 100644 --- a/packages/s2-core/src/utils/export/copy.ts +++ b/packages/s2-core/src/utils/export/copy.ts @@ -14,24 +14,24 @@ import { } from 'lodash'; import type { ColCell, RowCell } from '../../cell'; import { - type CellMeta, CellTypes, CopyType, EMPTY_PLACEHOLDER, EXTRA_FIELD, ID_SEPARATOR, InteractionStateName, - type RowData, SERIES_NUMBER_FIELD, VALUE_FIELD, + type CellMeta, + type RowData, } from '../../common'; import type { DataType } from '../../data-set/interface'; import type { Node } from '../../facet/layout/node'; import type { SpreadSheet } from '../../sheet-type'; import { copyToClipboard } from '../../utils/export'; import { flattenDeep } from '../data-set-operate'; +import { getHeaderTotalStatus } from '../dataset/pivot-data-set'; import { getEmptyPlaceholder } from '../text'; -import { getTotalStatusByRowCol } from '../dataset/pivot-data-set'; export function keyEqualTo(key: string, compareKey: string) { if (!key || !compareKey) { @@ -115,7 +115,7 @@ const getValueFromMeta = ( rowNode.isTotalMeasure || colNode.isTotals || colNode.isTotalMeasure, - totalStatus: getTotalStatusByRowCol(rowNode, colNode), + totalStatus: getHeaderTotalStatus(rowNode, colNode), }); return cell?.[VALUE_FIELD] ?? ''; } @@ -395,7 +395,7 @@ const getDataMatrix = ( rowNode.isTotalMeasure || colNode.isTotals || colNode.isTotalMeasure, - totalStatus: getTotalStatusByRowCol(rowNode, colNode), + totalStatus: getHeaderTotalStatus(rowNode, colNode), }); return getFormat( colNode.colIndex, @@ -660,6 +660,11 @@ function getLastLevelCells( }); } +/** 处理有合并单元格的复制(小记总计格) + * 维度1 | 维度2 | 维度3 + * 总计 | 维度三 + * => 总计 总计 维度三 + */ function getTotalCellMatrixId(meta: Node, maxLevel: number) { let nextNode = meta; let lastNode = { level: maxLevel }; @@ -667,7 +672,7 @@ function getTotalCellMatrixId(meta: Node, maxLevel: number) { while (nextNode.level >= 0) { let repeatNumber = lastNode.level - nextNode.level; while (repeatNumber > 0) { - cellId = nextNode.label + ID_SEPARATOR + cellId; + cellId = `${nextNode.label}${ID_SEPARATOR}${cellId}`; repeatNumber--; } lastNode = nextNode; diff --git a/packages/s2-core/src/utils/layout/add-totals.ts b/packages/s2-core/src/utils/layout/add-totals.ts index ed87291a18..9b29207ca1 100644 --- a/packages/s2-core/src/utils/layout/add-totals.ts +++ b/packages/s2-core/src/utils/layout/add-totals.ts @@ -15,7 +15,12 @@ export const addTotals = (params: TotalParams) => { // check to see if grand total is added if (totalsConfig?.showGrandTotals) { action = totalsConfig.reverseLayout ? 'unshift' : 'push'; - totalValue = new TotalClass(totalsConfig.label, false, true, true); + totalValue = new TotalClass({ + label: totalsConfig.label, + isSubTotals: false, + isGrandTotals: true, + isTotalRoot: true, + }); } } else if ( /** @@ -29,7 +34,12 @@ export const addTotals = (params: TotalParams) => { currentField !== EXTRA_FIELD ) { action = totalsConfig.reverseSubLayout ? 'unshift' : 'push'; - totalValue = new TotalClass(totalsConfig.subLabel, true, false, true); + totalValue = new TotalClass({ + label: totalsConfig.subLabel, + isSubTotals: true, + isGrandTotals: false, + isTotalRoot: true, + }); } fieldValues[action]?.(totalValue); diff --git a/packages/s2-core/src/utils/layout/generate-header-nodes.ts b/packages/s2-core/src/utils/layout/generate-header-nodes.ts index 402eb59120..aa33a7bf42 100644 --- a/packages/s2-core/src/utils/layout/generate-header-nodes.ts +++ b/packages/s2-core/src/utils/layout/generate-header-nodes.ts @@ -113,6 +113,7 @@ export const generateHeaderNodes = (params: HeaderNodesParams) => { // 如果当前是隐藏节点, 给其父节点挂载相应信息 (兄弟节点, 当前哪个子节点隐藏了), 这样在 facet 层可以直接使用, 不用每次都去遍历 const hiddenColumnsInfo = spreadsheet?.facet?.getHiddenColumnsInfo(node); if (hiddenColumnsInfo && parentNode) { + // hiddenChildNodeInfo 属性在 S2 中没有用到,但是没删怕外部有使用,已标记为废弃 parentNode.hiddenChildNodeInfo = hiddenColumnsInfo; } diff --git a/packages/s2-core/src/utils/sort-action.ts b/packages/s2-core/src/utils/sort-action.ts index 921df04fb0..79bdf3e52d 100644 --- a/packages/s2-core/src/utils/sort-action.ts +++ b/packages/s2-core/src/utils/sort-action.ts @@ -252,7 +252,13 @@ export const getSortByMeasureValues = ( const { dataSet, sortParam, originValues } = params; const { fields } = dataSet; const { sortByMeasure, query, sortFieldId } = sortParam; - const dataList = dataSet.getMultiData(query); // 按 query 查出所有数据 + const dataList = dataSet.getMultiData( + query, + undefined, + undefined, + undefined, + true, + ); // 按 query 查出所有数据 const columns = getLeafColumnsWithKey(fields.columns); /** * 按明细数据 diff --git a/packages/s2-core/src/utils/text.ts b/packages/s2-core/src/utils/text.ts index e0074ca635..833940b721 100644 --- a/packages/s2-core/src/utils/text.ts +++ b/packages/s2-core/src/utils/text.ts @@ -357,9 +357,7 @@ const calX = ( const getDrawStyle = (cell: S2CellType) => { const { isTotals } = cell.getMeta(); const isMeasureField = (cell as ColCell).isMeasureField?.(); - const cellStyle = cell.getStyle( - isMeasureField ? CellTypes.COL_CELL : CellTypes.DATA_CELL, - ); + const cellStyle = cell.getStyle(cell.cellType || CellTypes.DATA_CELL); let textStyle: TextTheme; if (isMeasureField) { diff --git a/packages/s2-react/CHANGELOG.md b/packages/s2-react/CHANGELOG.md index 3ccc0dc2e5..b739337a0d 100644 --- a/packages/s2-react/CHANGELOG.md +++ b/packages/s2-react/CHANGELOG.md @@ -1,4 +1,12 @@ -# [@antv/s2-react-v1.44.0-alpha.1](https://github.com/antvis/S2/compare/@antv/s2-react-v1.43.0...@antv/s2-react-v1.44.0-alpha.1) (2023-09-14) +# [@antv/s2-react-v1.44.0](https://github.com/antvis/S2/compare/@antv/s2-react-v1.43.0...@antv/s2-react-v1.44.0) (2023-09-22) + + +### Features + +* 对比值无波动时也显示灰色 ([#2351](https://github.com/antvis/S2/issues/2351)) ([12f2d02](https://github.com/antvis/S2/commit/12f2d0268d447ec99a1227ffedd5ed266d93e86b)) +* 小计/总计功能,支持按维度分组汇总 ([#2346](https://github.com/antvis/S2/issues/2346)) ([20e608f](https://github.com/antvis/S2/commit/20e608f2447e9ffdb135d98e4cc7f39f1cfb308d)) + +# [@antv/s2-react-v1.43.0](https://github.com/antvis/S2/compare/@antv/s2-react-v1.42.1...@antv/s2-react-v1.43.0) (2023-09-09) ### Bug Fixes diff --git a/packages/s2-react/package.json b/packages/s2-react/package.json index ed2884b9b8..202ab32399 100644 --- a/packages/s2-react/package.json +++ b/packages/s2-react/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "@antv/s2-react", - "version": "1.44.0-alpha.1", + "version": "1.44.0", "main": "lib/index.js", "unpkg": "dist/index.min.js", "module": "esm/index.js", @@ -71,7 +71,7 @@ "react-beautiful-dnd": "^13.1.0" }, "devDependencies": { - "@antv/s2": "1.51.0-alpha.1", + "@antv/s2": "*", "@antv/s2-shared": "*", "@ant-design/icons": "^4.7.0", "@testing-library/react": "^12.1.4", diff --git a/packages/s2-shared/package.json b/packages/s2-shared/package.json index 50290a79ab..2abeb7b9c0 100644 --- a/packages/s2-shared/package.json +++ b/packages/s2-shared/package.json @@ -15,7 +15,7 @@ "test:ci-coverage": "yarn test:coverage --maxWorkers=3" }, "devDependencies": { - "@antv/s2": "1.51.0-alpha.1", + "@antv/s2": "*", "lodash": "^4.17.21" } } diff --git a/packages/s2-vue/package.json b/packages/s2-vue/package.json index 99f5c04ef9..885f43d1b6 100644 --- a/packages/s2-vue/package.json +++ b/packages/s2-vue/package.json @@ -66,7 +66,7 @@ "lodash": "^4.17.21" }, "devDependencies": { - "@antv/s2": "1.51.0-alpha.1", + "@antv/s2": "*", "@antv/s2-shared": "*", "@testing-library/vue": "^6.5.1", "@vue/tsconfig": "^0.1.3", diff --git a/s2-site/docs/api/basic-class/base-data-set.en.md b/s2-site/docs/api/basic-class/base-data-set.en.md index 2f3b37a108..a6cba2cc77 100644 --- a/s2-site/docs/api/basic-class/base-data-set.en.md +++ b/s2-site/docs/api/basic-class/base-data-set.en.md @@ -8,25 +8,25 @@ Function description: tabular data set. [details](https://github.com/antvis/S2/b s2.dataSet.xx() ``` -| parameter | illustrate | type | Version | -| ------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | -| fields | field information | () => [Fields](/docs/api/general/S2DataConfig#fields) | | -| meta | Field meta information, including field name, formatting, etc. | () => [Meta\[\]](/docs/api/general/S2DataConfig#meta) | | -| originData | Raw data | () => [DataType\[\]](#datatype) | | -| totalData | summary data | () => [DataType\[\]](#datatype) | | -| indexesData | multidimensional index data | () => [DataType\[\]](#datatype) | | -| sortParams | sort configuration | () => [SortParams](/docs/api/general/S2DataConfig#sortparams) | | -| spreadsheet | Form example | () => [SpreadSheet](/docs/api/basic-class/spreadsheet) | | -| getFieldMeta | Get field metadata information | (field: string, meta?: [Meta\[\]](/docs/api/general/S2DataConfig#meta) ) => [Meta](/docs/api/general/S2DataConfig#meta) | | -| getFieldName | get field name | `() => string` | | -| getFieldFormatter | Get the field formatting function | `() => (v: string) => unknown` | | -| getFieldDescription | Get field description | `() => string` | | -| setDataCfg | Set data configuration | `(dataCfg: T extends true ?` [`S2DataConfig`](/docs/api/general/S2DataConfig) `: Partial<`[`S2DataConfig`](/docs/api/general/S2DataConfig)`>, reset?: T) => void` | The `reset` parameter needs to be used in `@antv/s2-v1.34.0` version | -| getDisplayDataSet | Get the currently displayed dataset | () => [DataType\[\]](#datatype) | | -| getDimensionValues | get dimension value | (filed: string, query?: [DataType](#datatype) ) => string\[] | | -| getCellData | Get a single cell data | (params: [CellDataParams](#celldataparams) ) => [DataType\[\]](#datatype) | | -| getMultiData | Get bulk cell data | (query: [DataType](#datatype) , isTotals?: boolean, isRow?: boolean, drillDownFields?: string\[]) => [DataType\[\]](#datatype) | | -| moreThanOneValue | Is there more than 1 value | () => [ViewMeta](#viewmeta) | | +| parameter | illustrate | type | Version | +| ------------------- | -------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -------------------------------------------------------------------- | +| fields | field information | () => [Fields](/docs/api/general/S2DataConfig#fields) | | +| meta | Field meta information, including field name, formatting, etc. | () => [Meta\[\]](/docs/api/general/S2DataConfig#meta) | | +| originData | Raw data | () => [DataType\[\]](#datatype) | | +| totalData | summary data | () => [DataType\[\]](#datatype) | | +| indexesData | multidimensional index data | () => [DataType\[\]](#datatype) | | +| sortParams | sort configuration | () => [SortParams](/docs/api/general/S2DataConfig#sortparams) | | +| spreadsheet | Form example | () => [SpreadSheet](/docs/api/basic-class/spreadsheet) | | +| getFieldMeta | Get field metadata information | (field: string, meta?: [Meta\[\]](/docs/api/general/S2DataConfig#meta) ) => [Meta](/docs/api/general/S2DataConfig#meta) | | +| getFieldName | get field name | `() => string` | | +| getFieldFormatter | Get the field formatting function | `() => (v: string) => unknown` | | +| getFieldDescription | Get field description | `() => string` | | +| setDataCfg | Set data configuration | `(dataCfg: T extends true ?` [`S2DataConfig`](/docs/api/general/S2DataConfig) `: Partial<`[`S2DataConfig`](/docs/api/general/S2DataConfig)`>, reset?: T) => void` | The `reset` parameter needs to be used in `@antv/s2-v1.34.0` version | +| getDisplayDataSet | Get the currently displayed dataset | () => [DataType\[\]](#datatype) | | +| getDimensionValues | get dimension value | (filed: string, query?: [DataType](#datatype) ) => string\[] | | +| getCellData | Get a single cell data | (params: [CellDataParams](#celldataparams) ) => [DataType\[\]](#datatype) | | +| getMultiData | Get bulk cell data | (query: [DataType](#datatype) , isTotals?: boolean, isRow?: boolean, drillDownFields?: string\[], includeTotalData:boolean) => [DataType\[\]](#datatype) | | +| moreThanOneValue | Is there more than 1 value | () => [ViewMeta](#viewmeta) | | ### DataType diff --git a/s2-site/docs/api/basic-class/base-data-set.zh.md b/s2-site/docs/api/basic-class/base-data-set.zh.md index 3b8523fc5a..115356e46a 100644 --- a/s2-site/docs/api/basic-class/base-data-set.zh.md +++ b/s2-site/docs/api/basic-class/base-data-set.zh.md @@ -26,8 +26,9 @@ s2.dataSet.getFieldName('type') | getDisplayDataSet | 获取当前显示的数据集 | () => [DataType[]](#datatype) | | | getDimensionValues | 获取维值 | (filed: string, query?: [DataType](#datatype) ) => string[] | | | getCellData | 获取单个的单元格数据 | (params: [CellDataParams](#celldataparams)) => [DataType[]](#datatype) | | -| getMultiData | 获取批量的单元格数据 | (query: [DataType](#datatype), isTotals?: boolean, isRow?: boolean, drillDownFields?: string[]) => [DataType[]](#datatype) | | +| getMultiData | 获取批量的单元格数据 | (query: [DataType](#datatype), isTotals?: boolean, isRow?: boolean, drillDownFields?: string[], includeTotalData:boolean) => [DataType[]](#datatype) | | | moreThanOneValue | 是否超过 1 个数值 | () => [ViewMeta](#viewmeta) | | +| isEmpty | 是否为空数据集 | () => `boolean` | `@antv/s2-v1.51.1` | ### DataType diff --git a/s2-site/docs/common/header-action-icon.zh.md b/s2-site/docs/common/header-action-icon.zh.md index bac525cb2b..e192e145e8 100644 --- a/s2-site/docs/common/header-action-icon.zh.md +++ b/s2-site/docs/common/header-action-icon.zh.md @@ -7,12 +7,12 @@ | 参数 | 说明 | 类型 | 默认值 | 必选 | 取值 | 版本 | | ---------------- | ----------- | ----------- | ------ | ---- | ----------- | --- | | iconNames | 已经注册的 icon 名称,或用户通过 customSVGIcons 注册的 icon 名称 | `string[]` | | ✓ | | | -| belongsCell | 需要增加操作图标的单元格名称 | `string[]` | | ✓ | 角头:'cornerCell';
列头:'colCell';
行头:'rowCell' | | -| defaultHide | 控制是否 hover 才展示 icon | `boolean | (meta: Node, iconName: string) => boolean` | false | | true | `1.26.0` 支持配置为一个函数 | -| displayCondition | 展示的过滤条件,可以通过该回调函数用户自定义行列头哪些层级或单元格需要展示 icon。 所有返回值为 true 的 icon 会展示给用户。 | `(mete: Node, iconName: string) => boolean;` | | | | `1.26.0` 回传 `iconName` 并按单个 icon 控制显隐 | -| action | icon 点击之后的执行函数 | `(headerActionIconProps: HeaderActionIconProps) => void;` | | | | 已废弃,请使用 `onClick` | -| onClick | icon 点击之后的执行函数 | `(headerIconClickParams: HeaderIconClickParams) => void;` | | | | `1.26.0` | -| onHover | icon hover 开始及结束之后的执行函数 | `(headerIconHoverParams: HeaderIconHoverParams) => void;` | | | | `1.26.0` | +| belongsCell | 需要增加操作图标的单元格名称 | `string` | | ✓ | 角头:`cornerCell`;
列头:`colCell`;
行头:`rowCell` | | +| defaultHide | 控制是否 hover 在对应单元格时才展示 icon, 默认始终展示 | `boolean \| (meta: Node, iconName: string) => boolean` | false | | | `1.26.0` 支持配置为一个函数 | +| displayCondition | 自定义展示条件,可根据当前单元格信息动态控制 icon 是否展示 | `(mete: Node, iconName: string) => boolean` | | | | `1.26.0` 回传 `iconName` 并按单个 icon 控制显隐 | +| action | icon 点击之后的执行函数 (已废弃,请使用 `onClick`) | `(headerActionIconProps: HeaderActionIconProps) => void` | | | | | +| onClick | icon 点击之后的执行函数 | `(headerIconClickParams: HeaderIconClickParams) => void` | | | | `1.26.0` | +| onHover | icon hover 开始及结束之后的执行函数 | `(headerIconHoverParams: HeaderIconHoverParams) => void` | | | | `1.26.0` | ​ @@ -24,9 +24,9 @@ | 参数 | 功能描述 | 类型 | 默认值 | 必选 | | --- | --- | --- | --- | --- | -| iconName | 当前点击的 icon 名称 | string | | ✓ | -| meta |当前 cell 的 meta 信息| Node | | ✓ | -| event |当前点击事件信息| Event |false| ✓ | +| iconName | 当前 icon 名称 | string | | ✓ | +| meta |当前 cell 的 meta 信息| [Node](/api/basic-class/node) | | ✓ | +| event |当前点击事件信息| Event | false | ✓ | ## CustomSVGIcon diff --git a/s2-site/docs/common/icon.zh.md b/s2-site/docs/common/icon.zh.md index 58a8b63450..9838273b7a 100644 --- a/s2-site/docs/common/icon.zh.md +++ b/s2-site/docs/common/icon.zh.md @@ -7,12 +7,12 @@ order: 3 | icon 名称 | icon 图标 | 功能描述 | icon 名称 | icon 图标 | 功能描述 | | ------------- | --------------------- | ---------- | ---------------- | ----------- | ------------------ | -| CellDown | icon | 同环比下降 | ExpandColIcon | icon | 明细表隐藏展开 | +| CellDown | icon | 同环比下降 | ExpandColIcon | icon | 展开列头 | | CellUp | icon | 同环比上升 | Plus | icon | 树状表格展开 | | GlobalAsc | icon | 全局升序 | Minus | icon | 树状表格收起 | -| GlobalDesc | icon | 全局降序 | SortDown | icon | 明细表降序 | -| GroupAsc | icon | 组内升序 | SortDownSelected | icon | 明细表降序选择状态 | -| GroupDesc | icon | 组内降序 | SortUp | icon | 明细表升序 | -| Trend | icon | 趋势图 | SortUpSelected | icon | 明细表升序选择状态 | +| GlobalDesc | icon | 全局降序 | SortDown | icon | 降序 | +| GroupAsc | icon | 组内升序 | SortDownSelected | icon | 降序选中状态 | +| GroupDesc | icon | 组内降序 | SortUp | icon | 升序 | +| Trend | icon | 趋势图 | SortUpSelected | icon | 升序选中状态 | | ArrowUp | icon | 指标上升 |ArrowDown | icon | 指标下降 | | DrillDownIcon | icon | 下钻 | | | | diff --git a/s2-site/docs/common/totals.en.md b/s2-site/docs/common/totals.en.md index 56f167b9cf..d9b8df6b75 100644 --- a/s2-site/docs/common/totals.en.md +++ b/s2-site/docs/common/totals.en.md @@ -27,8 +27,8 @@ object is **required** , *default: null* Function description: subtotal total co | subLabel | subtotal alias | `string` | | | | calcTotals | Custom Calculated Totals | [CalcTotals](#calctotals) | | | | calcSubTotals | Custom Calculated Subtotals | [CalcTotals](#calctotals) | | | -| totalsDimensionsGroup | grouping dimension of the total |`string[]` | | | -| subTotalsDimensionsGroup | grouping dimension of the subtotal | `string[]` | | | +| totalsGroupDimensions | grouping dimension of the total |`string[]` | | | +| subTotalsGroupDimensions | grouping dimension of the subtotal | `string[]` | | | ## CalcTotals diff --git a/s2-site/docs/common/totals.zh.md b/s2-site/docs/common/totals.zh.md index 86ada706c4..6edacd116a 100644 --- a/s2-site/docs/common/totals.zh.md +++ b/s2-site/docs/common/totals.zh.md @@ -27,8 +27,8 @@ object **必选**,_default:null_ 功能描述: 小计总计配置 | subLabel | 小计别名 | `string` | | | | calcTotals | 自定义计算总计 | [CalcTotals](#calctotals) | | | | calcSubTotals | 自定义计算小计 | [CalcTotals](#calctotals) | | | -| totalsDimensionsGroup | 总计的分组维度 |`string[]` | | | -| subTotalsDimensionsGroup | 小计的分组维度 | `string[]` | | | +| totalsGroupDimensions | 总计的分组维度 |`string[]` | | | +| subTotalsGroupDimensions | 小计的分组维度 | `string[]` | | | ## CalcTotals diff --git a/s2-site/docs/manual/basic/totals.en.md b/s2-site/docs/manual/basic/totals.en.md index da12aa68d8..805319532b 100644 --- a/s2-site/docs/manual/basic/totals.en.md +++ b/s2-site/docs/manual/basic/totals.en.md @@ -25,8 +25,8 @@ object is **required** , *default: null* Function description: Subtotal calculat | subLabel | subtotal alias | `string` | | | | | calcTotals | calculate the total | `CalcTotals` | | | | | calcSubTotals | calculate subtotal | `CalcTotals` | | | | -| totalsDimensionsGroup | grouping dimension of the total |`string[]` | | | -| subTotalsDimensionsGroup | grouping dimension of the subtotal | `string[]` | | | +| totalsGroupDimensions | grouping dimension of the total |`string[]` | | | +| subTotalsGroupDimensions | grouping dimension of the subtotal | `string[]` | | | ```typescript const s2Options = { @@ -37,8 +37,8 @@ object is **required** , *default: null* Function description: Subtotal calculat reverseLayout: true, reverseSubLayout: true, subTotalsDimensions: [ 'province' ], - totalsDimensionsGroup: ['city'], - subTotalsDimensionsGroup: ['type', 'sub_type'], + totalsGroupDimensions: ['city'], + subTotalsGroupDimensions: ['type', 'sub_type'], }, col: { showGrandTotals: true, diff --git a/s2-site/docs/manual/basic/totals.zh.md b/s2-site/docs/manual/basic/totals.zh.md index 4101ba6852..cffaf101cb 100644 --- a/s2-site/docs/manual/basic/totals.zh.md +++ b/s2-site/docs/manual/basic/totals.zh.md @@ -51,7 +51,7 @@ order: 5 按维度进行 小计/总计 的汇总计算,用于进行某一维度的数据对比分析等。 - + #### 行总计小计分组 @@ -61,7 +61,7 @@ order: 5 #### 列总计小计分组 -row +col ## 使用 @@ -93,8 +93,8 @@ object **必选**,_default:null_ 功能描述: 小计总计算配置 | subLabel | 小计别名 | `string` | | | | calcTotals | 计算总计 | `CalcTotals` | | | | calcSubTotals | 计算小计 | `CalcTotals` | | | -| totalsDimensionsGroup | 总计的分组维度 |`string[]` | | | -| subTotalsDimensionsGroup | 小计的分组维度 | `string[]` | | | +| totalsGroupDimensions | 总计的分组维度 |`string[]` | | | +| subTotalsGroupDimensions | 小计的分组维度 | `string[]` | | | ```ts const s2Options = { @@ -105,8 +105,8 @@ const s2Options = { reverseLayout: true, reverseSubLayout: true, subTotalsDimensions: ['province'], - totalsDimensionsGroup: ['city'], - subTotalsDimensionsGroup: ['type', 'sub_type'], + totalsGroupDimensions: ['city'], + subTotalsGroupDimensions: ['type', 'sub_type'], }, col: { showGrandTotals: true, diff --git a/s2-site/examples/analysis/totals/demo/dimension-group-col.ts b/s2-site/examples/analysis/totals/demo/dimension-group-col.ts index 00322e085a..89bd78473a 100644 --- a/s2-site/examples/analysis/totals/demo/dimension-group-col.ts +++ b/s2-site/examples/analysis/totals/demo/dimension-group-col.ts @@ -56,9 +56,9 @@ fetch('https://gw.alipayobjects.com/os/bmw-prod/6eede6eb-8021-4da8-bb12-67891a57 aggregation: 'SUM', }, // 总计分组下,city 城市维度会出现分组 - totalsDimensionsGroup: ['city'], + totalsGroupDimensions: ['city'], // 小计维度下,type 类别维度下会出现分组 - subTotalsDimensionsGroup: ['type'], + subTotalsGroupDimensions: ['type'], }, }, }; diff --git a/s2-site/examples/analysis/totals/demo/dimension-group-row.ts b/s2-site/examples/analysis/totals/demo/dimension-group-row.ts index 02cde55ef4..e12518827e 100644 --- a/s2-site/examples/analysis/totals/demo/dimension-group-row.ts +++ b/s2-site/examples/analysis/totals/demo/dimension-group-row.ts @@ -55,9 +55,9 @@ fetch('https://gw.alipayobjects.com/os/bmw-prod/6eede6eb-8021-4da8-bb12-67891a57 aggregation: 'SUM', }, // 总计分组下,city 城市维度会出现分组 - totalsDimensionsGroup: ['city'], + totalsGroupDimensions: ['city'], // 小计维度下,type 类别维度下会出现分组 - subTotalsDimensionsGroup: ['type'], + subTotalsGroupDimensions: ['type'], }, }, };