From 388157c0ecb82c45f8a0277b649e891366dc2f1c Mon Sep 17 00:00:00 2001 From: Jinke Li Date: Fri, 24 May 2024 16:20:41 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=BC=80=E5=90=AF?= =?UTF-8?q?=E5=A4=9A=E8=A1=8C=E6=96=87=E6=9C=AC=E6=97=B6=E7=9A=84=E5=B8=83?= =?UTF-8?q?=E5=B1=80=E6=80=A7=E8=83=BD=20(#2734)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 优化开启多行文本时的布局性能 * test: 增加 benchmark * perf: 优化明细表性能 * fix: 修复类型问题和错误定义 * fix: 重命名 * test: 增加 --detectOpenHandles * chore: 更新版本 --- .../__tests__/benchmark/pivot-sheet-spec.ts | 96 +++- .../__tests__/benchmark/table-sheet-spec.ts | 65 ++- .../multi-line-text-spec.ts.snap | 539 ++++++++++++++++++ .../spreadsheet/multi-line-text-spec.ts | 41 +- packages/s2-core/package.json | 8 +- packages/s2-core/scripts/test-live.mjs | 2 +- packages/s2-core/src/cell/base-cell.ts | 2 +- packages/s2-core/src/cell/col-cell.ts | 2 +- packages/s2-core/src/cell/corner-cell.ts | 2 +- packages/s2-core/src/cell/data-cell.ts | 10 +- packages/s2-core/src/cell/header-cell.ts | 2 +- packages/s2-core/src/cell/row-cell.ts | 2 +- .../s2-core/src/cell/series-number-cell.ts | 2 +- .../s2-core/src/common/interface/basic.ts | 20 +- .../s2-core/src/common/interface/s2Options.ts | 11 +- packages/s2-core/src/facet/base-facet.ts | 106 +++- packages/s2-core/src/facet/frozen-facet.ts | 5 +- .../s2-core/src/facet/header/table-col.ts | 5 +- packages/s2-core/src/facet/layout/node.ts | 2 + packages/s2-core/src/facet/pivot-facet.ts | 32 +- packages/s2-core/src/facet/table-facet.ts | 68 ++- .../data-cell-brush-selection.ts | 2 +- .../src/utils/interaction/merge-cell.ts | 2 +- packages/s2-react/package.json | 8 +- packages/s2-react/scripts/test-live.mjs | 2 +- packages/s2-shared/package.json | 2 +- packages/s2-vue/package.json | 2 +- pnpm-lock.yaml | 146 ++--- 28 files changed, 1009 insertions(+), 177 deletions(-) diff --git a/packages/s2-core/__tests__/benchmark/pivot-sheet-spec.ts b/packages/s2-core/__tests__/benchmark/pivot-sheet-spec.ts index 3f35969b22..766f95db66 100644 --- a/packages/s2-core/__tests__/benchmark/pivot-sheet-spec.ts +++ b/packages/s2-core/__tests__/benchmark/pivot-sheet-spec.ts @@ -1,10 +1,14 @@ /* eslint-disable no-console */ -import { PivotSheet, S2DataConfig } from '../../src'; +import { PivotSheet, S2DataConfig, type S2Options } from '../../src'; import { generateRawData, getContainer } from '../util/helpers'; -async function measurePivotSheetRender(s2DataCfg: S2DataConfig, title) { +async function measurePivotSheetRender( + title: string, + s2DataCfg: S2DataConfig, + s2Options?: S2Options, +) { performance.mark('startTask'); - const s2 = new PivotSheet(getContainer(), s2DataCfg, null); + const s2 = new PivotSheet(getContainer(), s2DataCfg, s2Options || null); await s2.render(); performance.mark('endTask'); @@ -40,8 +44,8 @@ describe('pivot sheet benchmark', () => { }; await measurePivotSheetRender( - s2DataCfg, '🚀 10 * 100 for single measure', + s2DataCfg, ); }); @@ -60,8 +64,8 @@ describe('pivot sheet benchmark', () => { }; await measurePivotSheetRender( - s2DataCfg, '🚀 100 * 100 for single measure', + s2DataCfg, ); }); @@ -80,8 +84,8 @@ describe('pivot sheet benchmark', () => { }; await measurePivotSheetRender( - s2DataCfg, '🚀 1000 * 100 for single measure', + s2DataCfg, ); }); @@ -100,8 +104,8 @@ describe('pivot sheet benchmark', () => { }; await measurePivotSheetRender( - s2DataCfg, '🚀 1000 * 1000 for single measure', + s2DataCfg, ); }); }); @@ -130,7 +134,7 @@ describe('pivot sheet benchmark', () => { ), }; - await measurePivotSheetRender(s2DataCfg, '🚀 10 * 100 for multi measure'); + await measurePivotSheetRender('🚀 10 * 100 for multi measure', s2DataCfg); }); test('should render 100 * 100', async () => { @@ -148,8 +152,8 @@ describe('pivot sheet benchmark', () => { }; await measurePivotSheetRender( - s2DataCfg, '🚀 100 * 100 for multi measure', + s2DataCfg, ); }); @@ -168,8 +172,8 @@ describe('pivot sheet benchmark', () => { }; await measurePivotSheetRender( - s2DataCfg, '🚀 1000 * 100 for multi measure', + s2DataCfg, ); }); @@ -188,8 +192,78 @@ describe('pivot sheet benchmark', () => { }; await measurePivotSheetRender( - s2DataCfg, '🚀 1000 * 1000 for multi measure', + s2DataCfg, + ); + }); + }); + + describe('multi line text', () => { + const baseDataCfg: S2DataConfig = { + fields: { + rows: ['province', 'city'], + columns: ['type', 'subType'], + values: ['number'], + }, + data: [], + }; + + const s2Options: S2Options = { + style: { + cornerCell: { + maxLines: 3, + }, + rowCell: { + maxLines: 3, + }, + colCell: { + maxLines: 3, + }, + dataCell: { + maxLines: 3, + }, + }, + }; + + test('should render 100 * 100', async () => { + const s2DataCfg: S2DataConfig = { + ...baseDataCfg, + data: generateRawData( + [ + ['province', 10], + ['city', 10], + ['type', 10], + ['subType', 10], + ], + ['number'], + ), + }; + + await measurePivotSheetRender( + '🚀 100 * 100 for single measure', + s2DataCfg, + s2Options, + ); + }); + + test('should render 1000 * 1000', async () => { + const s2DataCfg: S2DataConfig = { + ...baseDataCfg, + data: generateRawData( + [ + ['province', 100], + ['city', 10], + ['type', 100], + ['subType', 10], + ], + ['number'], + ), + }; + + await measurePivotSheetRender( + '🚀 1000 * 1000 for single measure', + s2DataCfg, + s2Options, ); }); }); diff --git a/packages/s2-core/__tests__/benchmark/table-sheet-spec.ts b/packages/s2-core/__tests__/benchmark/table-sheet-spec.ts index ab4a159ec3..bfbcfe8cdb 100644 --- a/packages/s2-core/__tests__/benchmark/table-sheet-spec.ts +++ b/packages/s2-core/__tests__/benchmark/table-sheet-spec.ts @@ -1,10 +1,14 @@ /* eslint-disable no-console */ -import { S2DataConfig, TableSheet } from '../../src'; +import { S2DataConfig, TableSheet, type S2Options } from '../../src'; import { generateRawData, getContainer } from '../util/helpers'; -async function measureTableSheetRender(s2DataCfg: S2DataConfig, title) { +async function measureTableSheetRender( + title: string, + s2DataCfg: S2DataConfig, + s2Options?: S2Options, +) { performance.mark('startTask'); - const s2 = new TableSheet(getContainer(), s2DataCfg, null); + const s2 = new TableSheet(getContainer(), s2DataCfg, s2Options || null); await s2.render(); performance.mark('endTask'); @@ -36,7 +40,7 @@ describe('table sheet benchmark', () => { ), }; - await measureTableSheetRender(s2DataCfg, '🚀 1000 items'); + await measureTableSheetRender('🚀 1000 items', s2DataCfg); }); test('should render 10000 items', async () => { @@ -53,7 +57,7 @@ describe('table sheet benchmark', () => { ), }; - await measureTableSheetRender(s2DataCfg, '🚀 10000 items'); + await measureTableSheetRender('🚀 10000 items', s2DataCfg); }); test('should render 10_0000 items', async () => { @@ -70,7 +74,7 @@ describe('table sheet benchmark', () => { ), }; - await measureTableSheetRender(s2DataCfg, '🚀 10_0000 items'); + await measureTableSheetRender('🚀 10_0000 items', s2DataCfg); }); test('should render 100_0000 items', async () => { @@ -87,6 +91,53 @@ describe('table sheet benchmark', () => { ), }; - await measureTableSheetRender(s2DataCfg, '🚀 100_0000 items'); + await measureTableSheetRender('🚀 100_0000 items', s2DataCfg); + }); + + describe('multi line text', () => { + const s2Options: S2Options = { + style: { + colCell: { + maxLines: 3, + }, + dataCell: { + maxLines: 3, + }, + }, + }; + + test('should render 10_0000 items', async () => { + const s2DataCfg: S2DataConfig = { + ...baseDataCfg, + data: generateRawData( + [ + ['province', 100], + ['city', 10], + ['type', 10], + ['subType', 10], + ], + ['number'], + ), + }; + + await measureTableSheetRender('🚀 10_0000 items', s2DataCfg, s2Options); + }); + + test('should render 100_0000 items', async () => { + const s2DataCfg: S2DataConfig = { + ...baseDataCfg, + data: generateRawData( + [ + ['province', 100], + ['city', 10], + ['type', 100], + ['subType', 10], + ], + ['number'], + ), + }; + + await measureTableSheetRender('🚀 100_0000 items', s2DataCfg, s2Options); + }); }); }); diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/multi-line-text-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/multi-line-text-spec.ts.snap index f23ed45c55..1f0a0eb37f 100644 --- a/packages/s2-core/__tests__/spreadsheet/__snapshots__/multi-line-text-spec.ts.snap +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/multi-line-text-spec.ts.snap @@ -11611,6 +11611,545 @@ Array [ ] `; +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and outside the canvas 1`] = ` +Array [ + Object { + "actualText": "序号", + "actualTextHeight": 16, + "actualTextWidth": 25, + "height": 72, + "multiLineActualTexts": Array [ + "序号", + ], + "originalText": "序号", + "width": 80, + }, +] +`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and outside the canvas 2`] = ` +Array [ + Object { + "actualText": "1", + "actualTextHeight": 15, + "actualTextWidth": 7, + "height": 360, + "multiLineActualTexts": Array [ + "1", + ], + "originalText": "1", + "width": 80, + }, +] +`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and outside the canvas 3`] = ` +Array [ + Object { + "actualText": "序号", + "actualTextHeight": 16, + "actualTextWidth": 25, + "height": 72, + "multiLineActualTexts": Array [ + "序号", + ], + "originalText": "序号", + "width": 80, + }, + Object { + "actualText": "省份", + "actualTextHeight": 16, + "actualTextWidth": 25, + "height": 72, + "multiLineActualTexts": Array [ + "省份", + ], + "originalText": "省份", + "width": 103.8, + }, + Object { + "actualText": "城市城市城市城市城市城市城市城市城市城市城市城市", + "actualTextHeight": 64, + "actualTextWidth": 292, + "height": 72, + "multiLineActualTexts": Array [ + "城市城市城市城", + "市城市城市城市", + "城市城市城市城", + "市城市", + ], + "originalText": "城市城市城市城市城市城市城市城市城市城市城市城市", + "width": 103.8, + }, + Object { + "actualText": "类别类别类别类别类别类别类别类别类别类别", + "actualTextHeight": 48, + "actualTextWidth": 243, + "height": 72, + "multiLineActualTexts": Array [ + "类别类别类别类", + "别类别类别类别", + "类别类别类别", + ], + "originalText": "类别类别类别类别类别类别类别类别类别类别", + "width": 103.8, + }, + Object { + "actualText": "子类别", + "actualTextHeight": 16, + "actualTextWidth": 37, + "height": 72, + "multiLineActualTexts": Array [ + "子类别", + ], + "originalText": "子类别", + "width": 103.8, + }, + Object { + "actualText": "数量数量数量数量数量数量数量数量数量数量数量", + "actualTextHeight": 64, + "actualTextWidth": 268, + "height": 72, + "multiLineActualTexts": Array [ + "数量数量数量数", + "量数量数量数量", + "数量数量数量数", + "量", + ], + "originalText": "数量数量数量数量数量数量数量数量数量数量数量", + "width": 103.8, + }, +] +`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and outside the canvas 4`] = `Array []`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and outside the canvas 5`] = ` +Array [ + Object { + "actualText": "1", + "actualTextHeight": 15, + "actualTextWidth": 7, + "height": 360, + "multiLineActualTexts": Array [ + "1", + ], + "originalText": "1", + "width": 80, + }, + Object { + "actualText": "浙江省", + "actualTextHeight": 15, + "actualTextWidth": 37, + "height": 360, + "multiLineActualTexts": Array [ + "浙江省", + ], + "originalText": "浙江省", + "width": 103.8, + }, + Object { + "actualText": "绍兴市", + "actualTextHeight": 15, + "actualTextWidth": 37, + "height": 360, + "multiLineActualTexts": Array [ + "绍兴市", + ], + "originalText": "绍兴市", + "width": 103.8, + }, + Object { + "actualText": "家具", + "actualTextHeight": 15, + "actualTextWidth": 25, + "height": 360, + "multiLineActualTexts": Array [ + "家具", + ], + "originalText": "家具", + "width": 103.8, + }, + Object { + "actualText": "桌子", + "actualTextHeight": 15, + "actualTextWidth": 25, + "height": 360, + "multiLineActualTexts": Array [ + "桌子", + ], + "originalText": "桌子", + "width": 103.8, + }, + Object { + "actualText": "236723672361111", + "actualTextHeight": 30, + "actualTextWidth": 100, + "height": 360, + "multiLineActualTexts": Array [ + "2367236723611", + "11", + ], + "originalText": "236723672361111", + "width": 103.8, + }, +] +`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and outside the canvas 6`] = ` +Array [ + 0, + 360, + 436, + 482, + 512, + 542, + 572, + 602, + 632, + 662, + 692, + 722, + 752, + 782, + 812, + 842, + 872, + 902, + 932, + 962, + 992, + 1022, + 1052, + 1082, + 1112, + 1142, + 1172, + 1202, + 1232, + 1262, + 1292, + 1322, + 1352, + 1382, +] +`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and partial outside the canvas 1`] = ` +Array [ + Object { + "actualText": "序号", + "actualTextHeight": 16, + "actualTextWidth": 25, + "height": 72, + "multiLineActualTexts": Array [ + "序号", + ], + "originalText": "序号", + "width": 80, + }, +] +`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and partial outside the canvas 2`] = ` +Array [ + Object { + "actualText": "1", + "actualTextHeight": 15, + "actualTextWidth": 7, + "height": 300, + "multiLineActualTexts": Array [ + "1", + ], + "originalText": "1", + "width": 80, + }, + Object { + "actualText": "2", + "actualTextHeight": 15, + "actualTextWidth": 7, + "height": 76, + "multiLineActualTexts": Array [ + "2", + ], + "originalText": "2", + "width": 80, + }, +] +`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and partial outside the canvas 3`] = ` +Array [ + Object { + "actualText": "序号", + "actualTextHeight": 16, + "actualTextWidth": 25, + "height": 72, + "multiLineActualTexts": Array [ + "序号", + ], + "originalText": "序号", + "width": 80, + }, + Object { + "actualText": "省份", + "actualTextHeight": 16, + "actualTextWidth": 25, + "height": 72, + "multiLineActualTexts": Array [ + "省份", + ], + "originalText": "省份", + "width": 103.8, + }, + Object { + "actualText": "城市城市城市城市城市城市城市城市城市城市城市城市", + "actualTextHeight": 64, + "actualTextWidth": 292, + "height": 72, + "multiLineActualTexts": Array [ + "城市城市城市城", + "市城市城市城市", + "城市城市城市城", + "市城市", + ], + "originalText": "城市城市城市城市城市城市城市城市城市城市城市城市", + "width": 103.8, + }, + Object { + "actualText": "类别类别类别类别类别类别类别类别类别类别", + "actualTextHeight": 48, + "actualTextWidth": 243, + "height": 72, + "multiLineActualTexts": Array [ + "类别类别类别类", + "别类别类别类别", + "类别类别类别", + ], + "originalText": "类别类别类别类别类别类别类别类别类别类别", + "width": 103.8, + }, + Object { + "actualText": "子类别", + "actualTextHeight": 16, + "actualTextWidth": 37, + "height": 72, + "multiLineActualTexts": Array [ + "子类别", + ], + "originalText": "子类别", + "width": 103.8, + }, + Object { + "actualText": "数量数量数量数量数量数量数量数量数量数量数量", + "actualTextHeight": 64, + "actualTextWidth": 268, + "height": 72, + "multiLineActualTexts": Array [ + "数量数量数量数", + "量数量数量数量", + "数量数量数量数", + "量", + ], + "originalText": "数量数量数量数量数量数量数量数量数量数量数量", + "width": 103.8, + }, +] +`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and partial outside the canvas 4`] = `Array []`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and partial outside the canvas 5`] = ` +Array [ + Object { + "actualText": "1", + "actualTextHeight": 15, + "actualTextWidth": 7, + "height": 300, + "multiLineActualTexts": Array [ + "1", + ], + "originalText": "1", + "width": 80, + }, + Object { + "actualText": "2", + "actualTextHeight": 15, + "actualTextWidth": 7, + "height": 76, + "multiLineActualTexts": Array [ + "2", + ], + "originalText": "2", + "width": 80, + }, + Object { + "actualText": "浙江省", + "actualTextHeight": 15, + "actualTextWidth": 37, + "height": 300, + "multiLineActualTexts": Array [ + "浙江省", + ], + "originalText": "浙江省", + "width": 103.8, + }, + Object { + "actualText": "浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省...", + "actualTextHeight": 60, + "actualTextWidth": 338, + "height": 76, + "multiLineActualTexts": Array [ + "浙江省浙江省浙", + "江省浙江省浙江", + "省浙江省浙江省", + "浙江省浙江省...", + ], + "originalText": "浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省浙江省", + "width": 103.8, + }, + Object { + "actualText": "绍兴市", + "actualTextHeight": 15, + "actualTextWidth": 37, + "height": 300, + "multiLineActualTexts": Array [ + "绍兴市", + ], + "originalText": "绍兴市", + "width": 103.8, + }, + Object { + "actualText": "杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市...", + "actualTextHeight": 60, + "actualTextWidth": 338, + "height": 76, + "multiLineActualTexts": Array [ + "杭州市杭州市杭", + "州市杭州市杭州", + "市杭州市杭州市", + "杭州市杭州市...", + ], + "originalText": "杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市杭州市", + "width": 103.8, + }, + Object { + "actualText": "家具", + "actualTextHeight": 15, + "actualTextWidth": 25, + "height": 300, + "multiLineActualTexts": Array [ + "家具", + ], + "originalText": "家具", + "width": 103.8, + }, + Object { + "actualText": "家具家具家具家具家具家具家具家具家具家具家具家具家具家具", + "actualTextHeight": 60, + "actualTextWidth": 340, + "height": 76, + "multiLineActualTexts": Array [ + "家具家具家具家", + "具家具家具家具", + "家具家具家具家", + "具家具家具家具", + ], + "originalText": "家具家具家具家具家具家具家具家具家具家具家具家具家具家具", + "width": 103.8, + }, + Object { + "actualText": "桌子", + "actualTextHeight": 15, + "actualTextWidth": 25, + "height": 300, + "multiLineActualTexts": Array [ + "桌子", + ], + "originalText": "桌子", + "width": 103.8, + }, + Object { + "actualText": "桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌...", + "actualTextHeight": 60, + "actualTextWidth": 338, + "height": 76, + "multiLineActualTexts": Array [ + "桌子桌子桌子桌", + "子桌子桌子桌子", + "桌子桌子桌子桌", + "子桌子桌子桌...", + ], + "originalText": "桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子桌子", + "width": 103.8, + }, + Object { + "actualText": "236723672361111", + "actualTextHeight": 30, + "actualTextWidth": 100, + "height": 300, + "multiLineActualTexts": Array [ + "2367236723611", + "11", + ], + "originalText": "236723672361111", + "width": 103.8, + }, + Object { + "actualText": "7789778977897789778977897789", + "actualTextHeight": 45, + "actualTextWidth": 189, + "height": 76, + "multiLineActualTexts": Array [ + "7789778977897", + "7897789778977", + "89", + ], + "originalText": "7789778977897789778977897789", + "width": 103.8, + }, +] +`; + +exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly data cell height if actual text lines is difference and partial outside the canvas 6`] = ` +Array [ + 0, + 300, + 376, + 422, + 452, + 482, + 512, + 542, + 572, + 602, + 632, + 662, + 692, + 722, + 752, + 782, + 812, + 842, + 872, + 902, + 932, + 962, + 992, + 1022, + 1052, + 1082, + 1112, + 1142, + 1172, + 1202, + 1232, + 1262, + 1292, + 1322, +] +`; + exports[`SpreadSheet Multi Line Text Tests TableSheet should calc correctly row cell height if actual text lines is difference 1`] = ` Array [ Object { diff --git a/packages/s2-core/__tests__/spreadsheet/multi-line-text-spec.ts b/packages/s2-core/__tests__/spreadsheet/multi-line-text-spec.ts index 8baa66ce60..bf07f1fb05 100644 --- a/packages/s2-core/__tests__/spreadsheet/multi-line-text-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/multi-line-text-spec.ts @@ -4,8 +4,9 @@ import { range } from 'lodash'; import { PivotSheet, TableSheet, - type SpreadSheet, EXTRA_FIELD, + type SpreadSheet, + type TableFacet, } from '../../src'; import type { CellTextWordWrapStyle, @@ -706,6 +707,44 @@ describe('SpreadSheet Multi Line Text Tests', () => { ).toBeTruthy(); }); + test('should calc correctly data cell height if actual text lines is difference and partial outside the canvas', async () => { + updateStyle(4); + s2.setOptions({ + style: { + rowCell: { + // 让第二行部分超出屏幕 + heightByField: { + 0: 300, + }, + }, + }, + }); + + await s2.render(); + + matchCellStyleSnapshot(); + expect((s2.facet as unknown as TableFacet).rowOffsets).toMatchSnapshot(); + }); + + test('should calc correctly data cell height if actual text lines is difference and outside the canvas', async () => { + updateStyle(4); + s2.setOptions({ + style: { + rowCell: { + // 让第二行超出屏幕 + heightByField: { + 0: 360, + }, + }, + }, + }); + + await s2.render(); + + matchCellStyleSnapshot(); + expect((s2.facet as unknown as TableFacet).rowOffsets).toMatchSnapshot(); + }); + test('should not force adaptive adjust row height if custom cell style less than actual text height by rowCell.heightByField', async () => { updateStyle(3); diff --git a/packages/s2-core/package.json b/packages/s2-core/package.json index 4b87ddcfc4..54cf9eb609 100644 --- a/packages/s2-core/package.json +++ b/packages/s2-core/package.json @@ -53,7 +53,7 @@ "watch": "rimraf esm && pnpm build:esm -w", "test:live": "node ./scripts/test-live.mjs", "sync-event": "node ./scripts/sync-event.mjs", - "test": "jest --passWithNoTests", + "test": "jest --passWithNoTests --detectOpenHandles", "test:coverage": "pnpm test -- --coverage", "test:ci": "pnpm test -- --maxWorkers=3", "test:ci-coverage": "pnpm test:coverage --maxWorkers=3", @@ -61,10 +61,10 @@ "tsc": "tsc --noEmit" }, "dependencies": { - "@antv/g-lite": "^2.0.4", + "@antv/g-lite": "^2.0.5", "@antv/event-emitter": "^0.1.3", - "@antv/g": "^6.0.5", - "@antv/g-canvas": "^2.0.4", + "@antv/g": "^6.0.6", + "@antv/g-canvas": "^2.0.6", "d3-ease": "^3.0.1", "d3-interpolate": "^1.3.2", "d3-timer": "^1.0.9", diff --git a/packages/s2-core/scripts/test-live.mjs b/packages/s2-core/scripts/test-live.mjs index 5d7685c40d..a29a794bc4 100644 --- a/packages/s2-core/scripts/test-live.mjs +++ b/packages/s2-core/scripts/test-live.mjs @@ -8,7 +8,7 @@ import { default as autoCompletePrompt } from 'inquirer-autocomplete-prompt'; inquirer.registerPrompt('autocomplete', autoCompletePrompt); function run(path) { - const command = `cross-env DEBUG_MODE=1 npx jest ${path}`; + const command = `cross-env DEBUG_MODE=1 npx jest ${path} --passWithNoTests --detectOpenHandles`; const jestSpinner = ora(`[测试运行中]: ${command}`).start(); try { diff --git a/packages/s2-core/src/cell/base-cell.ts b/packages/s2-core/src/cell/base-cell.ts index 768bf03028..ab18fdbf11 100644 --- a/packages/s2-core/src/cell/base-cell.ts +++ b/packages/s2-core/src/cell/base-cell.ts @@ -144,7 +144,7 @@ export abstract class BaseCell extends Group { protected abstract getFormattedFieldValue(): FormatResult; - protected abstract getMaxTextWidth(): number; + public abstract getMaxTextWidth(): number; protected abstract getTextPosition(): PointLike; diff --git a/packages/s2-core/src/cell/col-cell.ts b/packages/s2-core/src/cell/col-cell.ts index ae843dac63..1de3b3a9ec 100644 --- a/packages/s2-core/src/cell/col-cell.ts +++ b/packages/s2-core/src/cell/col-cell.ts @@ -82,7 +82,7 @@ export class ColCell extends HeaderCell { return super.getFormattedFieldValue(); } - protected getMaxTextWidth(): number { + public getMaxTextWidth(): number { const { width } = this.getBBoxByType(CellClipBox.CONTENT_BOX); return width - this.getActionAndConditionIconWidth(); diff --git a/packages/s2-core/src/cell/corner-cell.ts b/packages/s2-core/src/cell/corner-cell.ts index 28b9b9a12d..ad34548540 100644 --- a/packages/s2-core/src/cell/corner-cell.ts +++ b/packages/s2-core/src/cell/corner-cell.ts @@ -210,7 +210,7 @@ export class CornerCell extends HeaderCell { return this.showTreeIcon() ? size! + margin!.right! : 0; } - protected getMaxTextWidth(): number { + public getMaxTextWidth(): number { const { width } = this.getBBoxByType(CellClipBox.CONTENT_BOX); return ( diff --git a/packages/s2-core/src/cell/data-cell.ts b/packages/s2-core/src/cell/data-cell.ts index a2b3726865..9439556967 100644 --- a/packages/s2-core/src/cell/data-cell.ts +++ b/packages/s2-core/src/cell/data-cell.ts @@ -81,6 +81,10 @@ export class DataCell extends BaseCell { return CellType.DATA_CELL; } + public isShallowRender() { + return super.isShallowRender() || this.meta.shallowRender || false; + } + public isMultiData() { const fieldValue = this.getFieldValue(); @@ -258,7 +262,9 @@ export class DataCell extends BaseCell { public setMeta(viewMeta: Partial) { super.setMeta(viewMeta as ViewMeta); - this.initCell(); + if (!this.isShallowRender()) { + this.initCell(); + } } public drawTextShape() { @@ -378,7 +384,7 @@ export class DataCell extends BaseCell { }; } - protected getMaxTextWidth(): number { + public getMaxTextWidth(): number { const { width } = this.getBBoxByType(CellClipBox.CONTENT_BOX); return width - this.getActionAndConditionIconWidth(); diff --git a/packages/s2-core/src/cell/header-cell.ts b/packages/s2-core/src/cell/header-cell.ts index 84a1282405..bb79c8583e 100644 --- a/packages/s2-core/src/cell/header-cell.ts +++ b/packages/s2-core/src/cell/header-cell.ts @@ -75,7 +75,7 @@ export abstract class HeaderCell< } public isShallowRender() { - return this.headerConfig.shallowRender!; + return super.isShallowRender() || this.headerConfig.shallowRender!; } protected shouldInit() { diff --git a/packages/s2-core/src/cell/row-cell.ts b/packages/s2-core/src/cell/row-cell.ts index c4c4056634..883229a375 100644 --- a/packages/s2-core/src/cell/row-cell.ts +++ b/packages/s2-core/src/cell/row-cell.ts @@ -356,7 +356,7 @@ export class RowCell extends HeaderCell { ); } - protected getMaxTextWidth(): number { + public getMaxTextWidth(): number { const { width } = this.getBBoxByType(CellClipBox.CONTENT_BOX); return width - this.getTextIndent() - this.getActionAndConditionIconWidth(); diff --git a/packages/s2-core/src/cell/series-number-cell.ts b/packages/s2-core/src/cell/series-number-cell.ts index 661b77391b..a1b7b1aeaa 100644 --- a/packages/s2-core/src/cell/series-number-cell.ts +++ b/packages/s2-core/src/cell/series-number-cell.ts @@ -59,7 +59,7 @@ export class SeriesNumberCell extends HeaderCell { }; } - protected getMaxTextWidth(): number { + public getMaxTextWidth(): number { const { width } = this.getBBoxByType(CellClipBox.CONTENT_BOX); return width; diff --git a/packages/s2-core/src/common/interface/basic.ts b/packages/s2-core/src/common/interface/basic.ts index e279b1cf46..b50ac4791b 100644 --- a/packages/s2-core/src/common/interface/basic.ts +++ b/packages/s2-core/src/common/interface/basic.ts @@ -1,5 +1,5 @@ import type { FederatedPointerEvent as Event, PointLike } from '@antv/g'; -import type { DataCell, MergedCell } from '../../cell'; +import type { DataCell, MergedCell, TableDataCell } from '../../cell'; import type { CustomTreeNode, Data, @@ -368,13 +368,18 @@ export interface InternalFullyHeaderActionIcon extends HeaderActionIcon { isSortIcon?: boolean; } -export type CellCallback = ( - node: Node, - spreadsheet: SpreadSheet, - headerConfig: T, -) => K; +export type CellCallbackParams = + [node: Node, spreadsheet: SpreadSheet, headerConfig: T]; + +export type CellCallback< + T extends Partial, + K extends S2CellType, +> = (node: Node, spreadsheet: SpreadSheet, headerConfig: T) => K; -export type DataCellCallback = (viewMeta: ViewMeta) => DataCell; +export type DataCellCallback = ( + viewMeta: ViewMeta, + spreadsheet: SpreadSheet, +) => DataCell | TableDataCell; export type MergedCellCallback = ( spreadsheet: SpreadSheet, @@ -440,6 +445,7 @@ export interface ViewMeta { value?: string | number; query?: Query; isLeaf?: boolean; + shallowRender?: boolean; [key: string]: unknown; } diff --git a/packages/s2-core/src/common/interface/s2Options.ts b/packages/s2-core/src/common/interface/s2Options.ts index 7df4459ad8..af14ca25ed 100644 --- a/packages/s2-core/src/common/interface/s2Options.ts +++ b/packages/s2-core/src/common/interface/s2Options.ts @@ -1,5 +1,10 @@ import type { CanvasConfig } from '@antv/g'; -import type { CornerCell, RowCell, SeriesNumberCell } from '../../cell'; +import type { + ColCell, + CornerCell, + RowCell, + SeriesNumberCell, +} from '../../cell'; import type { CellCallback, CornerHeaderCallback, @@ -28,7 +33,7 @@ import type { import type { SpreadSheet } from '../../sheet-type'; import type { CustomSVGIcon, HeaderActionIcon } from './basic'; import type { Conditions } from './condition'; -import type { InteractionOptions, S2CellType } from './interaction'; +import type { InteractionOptions } from './interaction'; import type { S2Style } from './style'; import type { BaseTooltipOperatorMenuOptions, @@ -215,7 +220,7 @@ export interface S2BasicOptions< * 自定义列头单元格 * @see https://s2.antv.antgroup.com/examples/custom/custom-cell#col-cell */ - colCell?: CellCallback; + colCell?: CellCallback; /** * 自定义合并单元格 diff --git a/packages/s2-core/src/facet/base-facet.ts b/packages/s2-core/src/facet/base-facet.ts index badfdf93e3..e422535e15 100644 --- a/packages/s2-core/src/facet/base-facet.ts +++ b/packages/s2-core/src/facet/base-facet.ts @@ -23,6 +23,7 @@ import { last, maxBy, reduce, + size, sumBy, } from 'lodash'; import { @@ -49,6 +50,7 @@ import { KEY_GROUP_PANEL_SCROLL, KEY_GROUP_ROW_INDEX_RESIZE_AREA, KEY_GROUP_ROW_RESIZE_AREA, + NODE_ID_SEPARATOR, OriginEventType, PANEL_GROUP_GROUP_CONTAINER_Z_INDEX, PANEL_GROUP_SCROLL_GROUP_Z_INDEX, @@ -64,6 +66,7 @@ import { } from '../common/debug'; import type { AdjustLeafNodesParams, + CellCallbackParams, CellCustomSize, FrameConfig, GridInfo, @@ -97,6 +100,7 @@ import { Frame, RowHeader, SeriesNumberHeader, + type BaseHeaderConfig, type RowHeaderConfig, } from './header'; import type { Hierarchy } from './layout/hierarchy'; @@ -109,6 +113,7 @@ import { optimizeScrollXY, translateGroup, } from './utils'; +import type { TableColHeader } from './header/table-col'; export abstract class BaseFacet { // spreadsheet instance @@ -165,7 +170,7 @@ export abstract class BaseFacet { public rowHeader: RowHeader | null; - public columnHeader: ColHeader; + public columnHeader: ColHeader | TableColHeader; public cornerHeader: CornerHeader; @@ -175,6 +180,24 @@ export abstract class BaseFacet { public gridInfo: GridInfo; + protected textWrapNodeHeightCache: Map; + + protected textWrapTempRowCell: RowCell | DataCell; + + protected textWrapTempColCell: ColCell | TableColCell; + + protected abstract getRowCellInstance( + node: Node | ViewMeta, + spreadsheet: SpreadSheet, + config: Partial, + ): RowCell | DataCell; + + protected abstract getColCellInstance( + node: Node, + spreadsheet: SpreadSheet, + config: Partial, + ): ColCell; + protected abstract doLayout(): LayoutResult; protected abstract clip(scrollX: number, scrollY: number): void; @@ -225,6 +248,19 @@ export abstract class BaseFacet { }; }; + protected initTextWrapTemp() { + const node = {} as Node; + const args: CellCallbackParams = [ + node, + this.spreadsheet, + { shallowRender: true } as BaseHeaderConfig, + ]; + + this.textWrapTempRowCell = this.getRowCellInstance(...args); + this.textWrapTempColCell = this.getColCellInstance(...args); + this.textWrapNodeHeightCache = new Map(); + } + protected initGroups() { this.initBackgroundGroup(); this.initPanelGroups(); @@ -328,7 +364,11 @@ export abstract class BaseFacet { ); } - protected getColNodeHeight(colNode: Node, colsHierarchy: Hierarchy) { + protected getColNodeHeight( + colNode: Node, + colsHierarchy: Hierarchy, + useCache: boolean = true, + ) { if (!colNode) { return 0; } @@ -351,15 +391,12 @@ export abstract class BaseFacet { return defaultHeight; } - const CellInstance = this.spreadsheet.isTableMode() - ? TableColCell - : ColCell; - - const colCell = new CellInstance(colNode, this.spreadsheet, { - shallowRender: true, - }); - - return this.getCellAdaptiveHeight(colCell, defaultHeight); + return this.getNodeAdaptiveHeight( + colNode, + this.textWrapTempColCell, + defaultHeight, + useCache, + ); } protected getDefaultColNodeHeight( @@ -387,21 +424,47 @@ export abstract class BaseFacet { return Math.max(defaultHeight, sampleMaxHeight); } - protected getCellAdaptiveHeight(cell: S2CellType, defaultHeight: number = 0) { - if (!cell) { + protected getNodeAdaptiveHeight( + meta: Node | ViewMeta, + cell: S2CellType, + defaultHeight: number = 0, + useCache = true, + ) { + if (!meta) { return defaultHeight; } - const { padding } = cell.getStyle().cell; + // 共用一个单元格用于测量, 通过动态更新 meta 的方式, 避免数据量大时频繁实例化触发 GC + cell.setMeta({ ...meta, shallowRender: true } as Node & ViewMeta); + + const fieldValue = String(cell.getFieldValue()); + + if (!fieldValue) { + return defaultHeight; + } + + const maxTextWidth = Math.ceil(cell.getMaxTextWidth()); + // 相同文本长度, 并且单元格宽度一致, 无需再计算换行高度, 使用缓存 + const cacheKey = `${size(fieldValue)}${NODE_ID_SEPARATOR}${maxTextWidth}`; + const cacheHeight = this.textWrapNodeHeightCache.get(cacheKey); + + if (cacheHeight && useCache) { + return cacheHeight || defaultHeight; + } cell.drawTextShape(); + const { padding } = cell.getStyle().cell; const textHeight = cell.getActualTextHeight(); const adaptiveHeight = textHeight + padding.top + padding.bottom; + const height = + cell.isMultiLineText() && textHeight >= defaultHeight + ? adaptiveHeight + : defaultHeight; + + this.textWrapNodeHeightCache.set(cacheKey, height); - return cell.isMultiLineText() && textHeight >= defaultHeight - ? adaptiveHeight - : defaultHeight; + return height; } /** @@ -641,6 +704,7 @@ export abstract class BaseFacet { this.unbindEvents(); this.clearAllGroup(); this.preCellIndexes = null; + this.textWrapNodeHeightCache.clear(); cancelAnimationFrame(this.scrollFrameId!); } @@ -1404,7 +1468,10 @@ export abstract class BaseFacet { const viewMeta = this.getCellMeta(j, i); if (viewMeta) { - const cell = this.spreadsheet.options.dataCell?.(viewMeta)!; + const cell = this.spreadsheet.options.dataCell?.( + viewMeta, + this.spreadsheet, + )!; if (!cell) { return; @@ -1445,6 +1512,7 @@ export abstract class BaseFacet { }; protected init() { + this.initTextWrapTemp(); this.initGroups(); // layout DebuggerUtil.getInstance().debugCallback(DEBUG_HEADER_LAYOUT, () => { @@ -1560,7 +1628,7 @@ export abstract class BaseFacet { return this.rowHeader; } - protected getColHeader(): ColHeader { + protected getColHeader() { if (!this.columnHeader) { const { x, width, viewportHeight, viewportWidth } = this.panelBBox; diff --git a/packages/s2-core/src/facet/frozen-facet.ts b/packages/s2-core/src/facet/frozen-facet.ts index 30b28d6a4d..d71f133a51 100644 --- a/packages/s2-core/src/facet/frozen-facet.ts +++ b/packages/s2-core/src/facet/frozen-facet.ts @@ -303,7 +303,10 @@ export abstract class FrozenFacet extends BaseFacet { if (viewMeta) { viewMeta.isFrozenCorner = true; - const cell = this.spreadsheet.options.dataCell?.(viewMeta)!; + const cell = this.spreadsheet.options.dataCell?.( + viewMeta, + this.spreadsheet, + )!; group.appendChild(cell); } diff --git a/packages/s2-core/src/facet/header/table-col.ts b/packages/s2-core/src/facet/header/table-col.ts index a7fbf10918..0588b647cc 100644 --- a/packages/s2-core/src/facet/header/table-col.ts +++ b/packages/s2-core/src/facet/header/table-col.ts @@ -1,5 +1,5 @@ import { Group, Rect, type RectStyleProps } from '@antv/g'; -import { TableColCell, TableCornerCell } from '../../cell'; +import { TableColCell, TableCornerCell, type ColCell } from '../../cell'; import { FRONT_GROUND_GROUP_FROZEN_Z_INDEX, FrozenGroupType, @@ -52,7 +52,8 @@ export class TableColHeader extends ColHeader { ]; if (node.field === SERIES_NUMBER_FIELD) { - return seriesNumberCell?.(...args) || new TableCornerCell(...args); + return (seriesNumberCell?.(...args) || + new TableCornerCell(...args)) as ColCell; } return colCell?.(...args) || new TableColCell(...args); diff --git a/packages/s2-core/src/facet/layout/node.ts b/packages/s2-core/src/facet/layout/node.ts index 1d0c70e56f..68758f8563 100644 --- a/packages/s2-core/src/facet/layout/node.ts +++ b/packages/s2-core/src/facet/layout/node.ts @@ -135,6 +135,8 @@ export class Node { public isFrozen?: boolean; + public shallowRender?: boolean; + public extra?: { description?: string; isCustomNode?: boolean; diff --git a/packages/s2-core/src/facet/pivot-facet.ts b/packages/s2-core/src/facet/pivot-facet.ts index 403f54072e..8d190614ab 100644 --- a/packages/s2-core/src/facet/pivot-facet.ts +++ b/packages/s2-core/src/facet/pivot-facet.ts @@ -16,7 +16,7 @@ import { size, sumBy, } from 'lodash'; -import { RowCell, SeriesNumberCell } from '../cell'; +import { ColCell, RowCell, SeriesNumberCell } from '../cell'; import { DEFAULT_TREE_ROW_CELL_WIDTH, FRONT_GROUND_GROUP_FROZEN_Z_INDEX, @@ -31,7 +31,11 @@ import { import { EXTRA_FIELD, LayoutWidthType, VALUE_FIELD } from '../common/constant'; import { CellType } from '../common/constant/interaction'; import { DebuggerUtil } from '../common/debug'; -import type { LayoutResult, SimpleData } from '../common/interface'; +import type { + CellCallbackParams, + LayoutResult, + SimpleData, +} from '../common/interface'; import type { PivotDataSet } from '../data-set/pivot-data-set'; import { renderLine, safeJsonParse } from '../utils'; import { getDataCellId } from '../utils/cell/data-cell'; @@ -54,6 +58,14 @@ export class PivotFacet extends FrozenFacet { return this.spreadsheet.theme.rowCell!.cell; } + protected override getRowCellInstance(...args: CellCallbackParams) { + return this.spreadsheet.options.rowCell?.(...args) || new RowCell(...args); + } + + protected override getColCellInstance(...args: CellCallbackParams) { + return this.spreadsheet.options.colCell?.(...args) || new ColCell(...args); + } + protected doLayout(): LayoutResult { const { rowLeafNodes, colLeafNodes, rowsHierarchy, colsHierarchy } = this.buildAllHeaderHierarchy(); @@ -246,7 +258,11 @@ export class PivotFacet extends FrozenFacet { } // 数值置于行头时, 列头的总计即叶子节点, 此时应该用列高: https://github.com/antvis/S2/issues/1715 - const colNodeHeight = this.getColNodeHeight(currentNode, colsHierarchy); + const colNodeHeight = this.getColNodeHeight( + currentNode, + colsHierarchy, + false, + ); currentNode.height = currentNode.isGrandTotals && @@ -423,11 +439,11 @@ export class PivotFacet extends FrozenFacet { return defaultHeight || 0; } - const rowCell = new RowCell(rowNode, this.spreadsheet, { - shallowRender: true, - }); - - return this.getCellAdaptiveHeight(rowCell, defaultHeight); + return this.getNodeAdaptiveHeight( + rowNode, + this.textWrapTempRowCell, + defaultHeight, + ); } /** diff --git a/packages/s2-core/src/facet/table-facet.ts b/packages/s2-core/src/facet/table-facet.ts index 8b80fbd61a..8743f6b800 100644 --- a/packages/s2-core/src/facet/table-facet.ts +++ b/packages/s2-core/src/facet/table-facet.ts @@ -9,7 +9,8 @@ import { maxBy, set, } from 'lodash'; -import { TableDataCell, TableSeriesNumberCell } from '../cell'; +import { TableColCell, TableDataCell, TableSeriesNumberCell } from '../cell'; +import { i18n } from '../common'; import { EMPTY_PLACEHOLDER_GROUP_CONTAINER_Z_INDEX, KEY_GROUP_EMPTY_PLACEHOLDER, @@ -21,6 +22,7 @@ import { } from '../common/constant'; import { DebuggerUtil } from '../common/debug'; import type { + CellCallbackParams, DataItem, FilterParam, LayoutResult, @@ -40,10 +42,10 @@ import { getIndexRangeWithOffsets } from '../utils/facet'; import { getAllChildCells } from '../utils/get-all-child-cells'; import { getValidFrozenOptions } from '../utils/layout/frozen'; import { floor } from '../utils/math'; -import { i18n } from '../common'; +import type { BaseFacet } from './base-facet'; import { CornerBBox } from './bbox/corner-bbox'; import { FrozenFacet } from './frozen-facet'; -import { ColHeader, Frame } from './header'; +import { Frame } from './header'; import { TableColHeader } from './header/table-col'; import { buildHeaderHierarchy } from './layout/build-header-hierarchy'; import { Hierarchy } from './layout/hierarchy'; @@ -54,12 +56,29 @@ import { getFrozenLeafNodesCount, isFrozenTrailingRow } from './utils'; export class TableFacet extends FrozenFacet { public emptyPlaceholderGroup: Group; + private lastRowOffset: number; + public constructor(spreadsheet: SpreadSheet) { super(spreadsheet); this.spreadsheet.on(S2Event.RANGE_SORT, this.onSortHandler); this.spreadsheet.on(S2Event.RANGE_FILTER, this.onFilterHandler); } + protected override getRowCellInstance(node: ViewMeta) { + const { dataCell } = this.spreadsheet.options; + + return ( + dataCell?.(node, this.spreadsheet) || + new TableDataCell(node, this.spreadsheet) + ); + } + + protected override getColCellInstance(...args: CellCallbackParams) { + const { colCell } = this.spreadsheet.options; + + return colCell?.(...args) || new TableColCell(...args); + } + protected initGroups() { super.initGroups(); this.initEmptyPlaceholderGroup(); @@ -154,12 +173,13 @@ export class TableFacet extends FrozenFacet { return rowHeight || 0; } - const dataCell = new TableDataCell(viewMeta, this.spreadsheet, { - shallowRender: true, - }); const defaultHeight = this.getCellHeightByRowIndex(viewMeta?.rowIndex); - return this.getCellAdaptiveHeight(dataCell, defaultHeight); + return this.getNodeAdaptiveHeight( + viewMeta, + this.textWrapTempRowCell, + defaultHeight, + ); } private getCellHeightByRowIndex(rowIndex: number) { @@ -192,15 +212,13 @@ export class TableFacet extends FrozenFacet { ); // getCellHeightByRowIndex 会优先读取 heightByField, 保持逻辑统一 - this.spreadsheet.setOptions({ - style: { - rowCell: { - heightByField: { - [rowIndex]: maxDataCellHeight || this.getDefaultCellHeight(), - }, - }, - }, - }); + const height = maxDataCellHeight || this.getDefaultCellHeight(); + + set( + this.spreadsheet.options, + `style.rowCell.heightByField.${rowIndex}`, + height, + ); } protected initRowOffsets() { @@ -210,16 +228,17 @@ export class TableFacet extends FrozenFacet { if (keys(heightByField!).length || style?.dataCell?.maxLines! > 1) { const data = this.spreadsheet.dataSet.getDisplayDataSet(); + this.textWrapNodeHeightCache.clear(); this.rowOffsets = [0]; - let lastOffset = 0; + this.lastRowOffset = 0; data.forEach((_, rowIndex) => { this.presetRowCellHeightIfNeeded(rowIndex); const currentHeight = this.getCellHeightByRowIndex(rowIndex); - const currentOffset = lastOffset + currentHeight; + const currentOffset = this.lastRowOffset + currentHeight; this.rowOffsets.push(currentOffset); - lastOffset = currentOffset; + this.lastRowOffset = currentOffset; }); } } @@ -312,8 +331,7 @@ export class TableFacet extends FrozenFacet { const { colsHierarchy } = this.getLayoutResult(); const height = floor(colsHierarchy.height); - this.cornerBBox = new CornerBBox(this); - + this.cornerBBox = new CornerBBox(this as unknown as BaseFacet); this.cornerBBox.height = height; this.cornerBBox.maxY = height; } @@ -471,7 +489,11 @@ export class TableFacet extends FrozenFacet { currentNode?.parent?.y! + currentNode?.parent?.height! ?? 0; } - currentNode.height = this.getColNodeHeight(currentNode, colsHierarchy); + currentNode.height = this.getColNodeHeight( + currentNode, + colsHierarchy, + false, + ); }); } @@ -720,7 +742,7 @@ export class TableFacet extends FrozenFacet { return null; } - protected getColHeader(): ColHeader { + protected getColHeader() { if (!this.columnHeader) { const { x, width, viewportHeight, viewportWidth } = this.panelBBox; diff --git a/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts b/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts index 3e3de10588..49acbdccd5 100644 --- a/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection/data-cell-brush-selection.ts @@ -136,7 +136,7 @@ export class DataCellBrushSelection extends BaseBrushSelection { meta.colIndex, )!; - return this.spreadsheet.options.dataCell!(viewMeta); + return this.spreadsheet.options.dataCell!(viewMeta, this.spreadsheet); }); } diff --git a/packages/s2-core/src/utils/interaction/merge-cell.ts b/packages/s2-core/src/utils/interaction/merge-cell.ts index a58bbafb3c..42dca5194e 100644 --- a/packages/s2-core/src/utils/interaction/merge-cell.ts +++ b/packages/s2-core/src/utils/interaction/merge-cell.ts @@ -178,7 +178,7 @@ export const getInvisibleInfo = ( ); if (meta) { - const cell = sheet?.options?.dataCell?.(meta); + const cell = sheet?.options?.dataCell?.(meta, meta.spreadsheet); viewMeta = cellInfo?.showText ? meta : viewMeta; cells.push(cell!); diff --git a/packages/s2-react/package.json b/packages/s2-react/package.json index b2f6e7eeef..7c29a897bf 100644 --- a/packages/s2-react/package.json +++ b/packages/s2-react/package.json @@ -49,7 +49,7 @@ "watch": "rimraf esm && pnpm build:esm -w", "dts:build": "tsc -p tsconfig.declaration.json", "dts:extract": "cross-env LIB=s2-react node ../../scripts/dts.js", - "test": "jest --passWithNoTests", + "test": "jest --passWithNoTests --detectOpenHandles", "test:coverage": "pnpm test -- --coverage", "test:ci": "pnpm test -- --maxWorkers=3", "test:ci-coverage": "pnpm test:coverage --maxWorkers=3", @@ -73,9 +73,9 @@ "devDependencies": { "@ant-design/icons": "^5.3.7", "@antv/event-emitter": "^0.1.3", - "@antv/g": "^6.0.5", - "@antv/g-plugin-a11y": "^1.0.5", - "@antv/g-plugin-rough-canvas-renderer": "^2.0.5", + "@antv/g": "^6.0.6", + "@antv/g-plugin-a11y": "^1.0.6", + "@antv/g-plugin-rough-canvas-renderer": "^2.0.7", "@antv/g2": "^5.1.20", "@antv/s2": "workspace:*", "@antv/s2-shared": "workspace:*", diff --git a/packages/s2-react/scripts/test-live.mjs b/packages/s2-react/scripts/test-live.mjs index f5a49aba62..d714003609 100644 --- a/packages/s2-react/scripts/test-live.mjs +++ b/packages/s2-react/scripts/test-live.mjs @@ -8,7 +8,7 @@ import { default as autoCompletePrompt } from 'inquirer-autocomplete-prompt'; inquirer.registerPrompt('autocomplete', autoCompletePrompt); function run(path) { - const command = `cross-env DEBUG_MODE=1 npx jest ${path}`; + const command = `cross-env DEBUG_MODE=1 npx jest ${path} --passWithNoTests --detectOpenHandles`; const jestSpinner = ora(`[测试运行中]: ${command}`).start(); try { diff --git a/packages/s2-shared/package.json b/packages/s2-shared/package.json index 56fa5e607d..f09f44dc74 100644 --- a/packages/s2-shared/package.json +++ b/packages/s2-shared/package.json @@ -10,7 +10,7 @@ "build": "run-s clean build:dts", "clean": "rimraf temp", "build:dts": "tsc -p tsconfig.declaration.json", - "test": "jest", + "test": "jest --passWithNoTests --detectOpenHandles", "test:coverage": "pnpm test -- --coverage", "test:ci-coverage": "pnpm test:coverage --maxWorkers=3" }, diff --git a/packages/s2-vue/package.json b/packages/s2-vue/package.json index c336aa5d86..037ac2b07c 100644 --- a/packages/s2-vue/package.json +++ b/packages/s2-vue/package.json @@ -49,7 +49,7 @@ "build:size-limit-json": "pnpm build:size-limit -- --json", "dts:build": "vue-tsc -p tsconfig.declaration.json", "dts:extract": "cross-env LIB=s2-vue node ../../scripts/dts.js", - "test": "jest --passWithNoTests", + "test": "jest --passWithNoTests --detectOpenHandles", "test:coverage": "pnpm test -- --coverage", "test:ci": "pnpm test -- --maxWorkers=3", "test:ci-coverage": "pnpm test:coverage --maxWorkers=3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac614f5623..d9f44802c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,14 +271,14 @@ importers: specifier: ^0.1.3 version: 0.1.3 '@antv/g': - specifier: ^6.0.5 - version: 6.0.5 + specifier: ^6.0.6 + version: 6.0.6 '@antv/g-canvas': - specifier: ^2.0.4 - version: 2.0.4 + specifier: ^2.0.6 + version: 2.0.6 '@antv/g-lite': - specifier: ^2.0.4 - version: 2.0.4 + specifier: ^2.0.5 + version: 2.0.5 d3-ease: specifier: ^3.0.1 version: 3.0.1 @@ -345,14 +345,14 @@ importers: specifier: ^0.1.3 version: 0.1.3 '@antv/g': - specifier: ^6.0.5 - version: 6.0.5 + specifier: ^6.0.6 + version: 6.0.6 '@antv/g-plugin-a11y': - specifier: ^1.0.5 - version: 1.0.5 + specifier: ^1.0.6 + version: 1.0.6 '@antv/g-plugin-rough-canvas-renderer': - specifier: ^2.0.5 - version: 2.0.5 + specifier: ^2.0.7 + version: 2.0.7 '@antv/g2': specifier: ^5.1.20 version: 5.1.20 @@ -841,7 +841,7 @@ packages: /@antv/component@2.0.0: resolution: {integrity: sha512-wl1pmqLIW/Zt9p7twMcAlvowHU81iV4bb5XWS38DL7gimSONHRh5C/uYKCXKB1KtHAH5d25YHESoGMBc0L3r+w==} dependencies: - '@antv/g': 6.0.5 + '@antv/g': 6.0.6 '@antv/scale': 0.4.15 '@antv/util': 3.3.7 svg-path-parser: 1.1.0 @@ -927,10 +927,10 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-camera-api@2.0.4: - resolution: {integrity: sha512-cYDfiluVwSbfXp6KTcBTpagVDenSloqZnWoIUv6sYkRyyf4hKZRSRE/oFyMQv6FyzdONiyowe8VU7sbmV8ePeQ==} + /@antv/g-camera-api@2.0.5: + resolution: {integrity: sha512-cU4eH5YGfdVM7XKAVcAXmVgkJMFJfhCFgSletNvYhzY1HTpBvcD9LIIP2TAomN+quAMuP0n48orUBr9S4iaOvw==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 '@antv/util': 3.3.7 gl-matrix: 3.4.3 tslib: 2.6.2 @@ -949,16 +949,16 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-canvas@2.0.4: - resolution: {integrity: sha512-DA1Ru1sASV4eJIIL17+Thn6da6omPNcSZ4Lyl3ZgakRpXsXhIC6dzMT+loBgD5OUS6vqRNoRqqJI6htgGkI2ew==} + /@antv/g-canvas@2.0.6: + resolution: {integrity: sha512-Jm9WQc41h96bZS/A40XNW8MjwY7WbMc8YQOmnWF/3uaUYehwssu5Fu1BjRNUFQeP0tSgU0LOTV7ZtnRJiT6Xdg==} dependencies: - '@antv/g-lite': 2.0.4 - '@antv/g-plugin-canvas-path-generator': 2.0.4 - '@antv/g-plugin-canvas-picker': 2.0.4 - '@antv/g-plugin-canvas-renderer': 2.0.4 - '@antv/g-plugin-dom-interaction': 2.0.4 - '@antv/g-plugin-html-renderer': 2.0.4 - '@antv/g-plugin-image-loader': 2.0.4 + '@antv/g-lite': 2.0.5 + '@antv/g-plugin-canvas-path-generator': 2.0.5 + '@antv/g-plugin-canvas-picker': 2.0.6 + '@antv/g-plugin-canvas-renderer': 2.0.6 + '@antv/g-plugin-dom-interaction': 2.0.5 + '@antv/g-plugin-html-renderer': 2.0.5 + '@antv/g-plugin-image-loader': 2.0.5 '@antv/util': 3.3.7 tslib: 2.6.2 @@ -968,10 +968,10 @@ packages: '@antv/g-lite': 2.0.3 dev: false - /@antv/g-dom-mutation-observer-api@2.0.4: - resolution: {integrity: sha512-UuDA8jagEvjvSLfF6YdGt2YacAaz9N5XXcnaGyikfvvCcayga5J2BNB1WJwVDrMccrvvXEr7i6PmcZ7X9qXUzg==} + /@antv/g-dom-mutation-observer-api@2.0.5: + resolution: {integrity: sha512-0e86+x/2hcmt5SITzeZjSA8X2/j2Fms+FQX+Iuf7zSou/77kk3ZqUASlwDsf03MT2oGXAW7EcO2Xy8MqpUiseA==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 /@antv/g-lite@2.0.3: resolution: {integrity: sha512-6BWtQmdn8m6evb02eT06qWCgRTl9Xt4HEMT8sqdiuTClLqSsnsY3sGgELNBkgHgjTB2S4KonXza0ltiqwnu4Hg==} @@ -984,12 +984,12 @@ packages: rbush: 3.0.1 tslib: 2.6.2 - /@antv/g-lite@2.0.4: - resolution: {integrity: sha512-2X8b0E5JtdoSk+ixdBp7scZ0rpkN4hbtlsQpAVn3O41O90mOAOkHQ0jD4arL66WVb0UqBo87kTBoMP1I9N+jzg==} + /@antv/g-lite@2.0.5: + resolution: {integrity: sha512-IeD7L10MOofNg302Zrru09zjNczCyOAC6mFLjHQlkYCQRtcU04zn32pTxCDy7xRkLHlhAK1mlymBqzeRMkmrRg==} dependencies: '@antv/g-math': 3.0.0 '@antv/util': 3.3.7 - d3-color: 1.4.1 + d3-color: 3.1.0 eventemitter3: 5.0.1 gl-matrix: 3.4.3 rbush: 3.0.1 @@ -1009,10 +1009,10 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-plugin-a11y@1.0.5: - resolution: {integrity: sha512-994WXceCw/MJBZjRpNuyMOxgkwKVWn5QwXzhA+7+n6Fjgt1WcSoJuWelkcAKc7MUKysn0tP+ynLFdfRWS8FHdQ==} + /@antv/g-plugin-a11y@1.0.6: + resolution: {integrity: sha512-/SKMwE4toALX64s5ZxWoiO4ag43UxcBFzalMkNnZj7HO7vjoCBa3RqzY+DJDegcJRwKtF9tgIMDjR+bImJIgpg==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 tslib: 2.6.2 dev: true @@ -1025,10 +1025,10 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-plugin-canvas-path-generator@2.0.4: - resolution: {integrity: sha512-hiGQphLKnjyRC2tioS9txoDbm6m+JcKeQCZacucoX3Sw3gW1C921OoeUHQr9XznbxPU9/+k5lBNnEHrD0ZAoWg==} + /@antv/g-plugin-canvas-path-generator@2.0.5: + resolution: {integrity: sha512-O1TCCmrzJDWrA9BG2MfPb79zY23a2fOugygeAaj9CElc/rO5ZqZ4lO4NJe4UCHRInTTXJjGwPnIcmX24Gi6O/A==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 '@antv/g-math': 3.0.0 '@antv/util': 3.3.7 tslib: 2.6.2 @@ -1045,13 +1045,13 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-plugin-canvas-picker@2.0.4: - resolution: {integrity: sha512-uAfN2co/peMm6TSYccPDs2obw+hw9c93sPBFhHTlHO+qBqnXRnchTWHSJosepZNLo4eRbgIO5C/KNSQZQg+nBg==} + /@antv/g-plugin-canvas-picker@2.0.6: + resolution: {integrity: sha512-QD9Z6YA29iJC36mQxd+qSX5tNlQZ41M4nGWY/iepKnOwAf9Rm+X4AgyIxKOrjg/rkRgUv2WR0Dp2eMfKUqm/Ng==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 '@antv/g-math': 3.0.0 - '@antv/g-plugin-canvas-path-generator': 2.0.4 - '@antv/g-plugin-canvas-renderer': 2.0.4 + '@antv/g-plugin-canvas-path-generator': 2.0.5 + '@antv/g-plugin-canvas-renderer': 2.0.6 '@antv/util': 3.3.7 gl-matrix: 3.4.3 tslib: 2.6.2 @@ -1068,13 +1068,13 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-plugin-canvas-renderer@2.0.4: - resolution: {integrity: sha512-2Hqwmmttp1HPV/VwuwaXMjvD2FPaHaVGqAQ6FJF7dLllTBJCpTZAgm8mpXfnP0USa394KILs3f5fY7K53kpfAw==} + /@antv/g-plugin-canvas-renderer@2.0.6: + resolution: {integrity: sha512-65uoJ2XJcUeGDYxnMxsGkdvaNNmEy79QEstr//as8hH+ssyUZJhBLgsDnhLqBQWYQGb7fXMPG0rUEesfjGOYkg==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 '@antv/g-math': 3.0.0 - '@antv/g-plugin-canvas-path-generator': 2.0.4 - '@antv/g-plugin-image-loader': 2.0.4 + '@antv/g-plugin-canvas-path-generator': 2.0.5 + '@antv/g-plugin-image-loader': 2.0.5 '@antv/util': 3.3.7 gl-matrix: 3.4.3 tslib: 2.6.2 @@ -1086,10 +1086,10 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-plugin-dom-interaction@2.0.4: - resolution: {integrity: sha512-3FE8vYj5u6NCvt1N6QCLHXBQUwCiKSopZUZFlWjVA7wadOyUwPHZv70gmRY1cbpEnkOVVfyyrQ9wNOr5A0qmSA==} + /@antv/g-plugin-dom-interaction@2.0.5: + resolution: {integrity: sha512-m4LeXM63d+MqQguCgmZU8TvvfuLlZ9zrtKWtRr6gYhi0+98o/3+pPrluIZhZHdgplH209+nN2JQ/ceoWp4OrZA==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 tslib: 2.6.2 /@antv/g-plugin-dragndrop@2.0.3: @@ -1108,10 +1108,10 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-plugin-html-renderer@2.0.4: - resolution: {integrity: sha512-KS1AT/TNOCMaNttWvfam8X5JC7LePYX5+OFp773Qpfo9tESEmZe+ooiXWhNaV8susaww3DwsW4kvETB1SBr5Rw==} + /@antv/g-plugin-html-renderer@2.0.5: + resolution: {integrity: sha512-xXgVQGIw/V23Bmh9uoFcdsTGdLGqa5afi3zxgsyC1L6bID2s2YjOVqVNoCLBz2NyzmUFISc70vyYxEMK+NQYQA==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 '@antv/util': 3.3.7 gl-matrix: 3.4.3 tslib: 2.6.2 @@ -1125,10 +1125,10 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-plugin-image-loader@2.0.4: - resolution: {integrity: sha512-maWyUhywmpPfcV2ra+iLSNUu82UEULQrRYoRAvrT4BF6XAV1VQ+Sow/uW2OS0GAtcntnXobkxyGhMylMBzfaog==} + /@antv/g-plugin-image-loader@2.0.5: + resolution: {integrity: sha512-TMpwOW4KibMtKOZZVMZHny+LOrWGjl1GP+i3N8sQx97oDaxIoNIB789XriuovKU/S71Y77ZTrqQdldknwWY24A==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 '@antv/util': 3.3.7 gl-matrix: 3.4.3 tslib: 2.6.2 @@ -1143,11 +1143,11 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-plugin-rough-canvas-renderer@2.0.5: - resolution: {integrity: sha512-0wUsC7uaOUUZtBnNb3aJkuauAX8igoxP+VpIvW1pWWg/b1FiDb38HyVTRAacjasT0VOYloHTQ0F1vixWrNCvhQ==} + /@antv/g-plugin-rough-canvas-renderer@2.0.7: + resolution: {integrity: sha512-YAgptSylljhJ/ttLuvAPBKtYtXReM/E6aA/OmexWoBX/2seDtNIkIfsMvG/ZISFDGHFq/SFdnGfBxS3P4DUl6w==} dependencies: - '@antv/g-canvas': 2.0.4 - '@antv/g-lite': 2.0.4 + '@antv/g-canvas': 2.0.6 + '@antv/g-lite': 2.0.5 '@antv/util': 3.3.7 roughjs: 4.6.6 tslib: 2.6.2 @@ -1161,10 +1161,10 @@ packages: tslib: 2.6.2 dev: false - /@antv/g-web-animations-api@2.0.5: - resolution: {integrity: sha512-J05EABt1kg1DxFU1eNDlkPX6HhQ1oauHVh8o5/xRqAyqmK2dGOzYJNftsaaY7vmyrRBxRALHheclDj8kaXKlhg==} + /@antv/g-web-animations-api@2.0.6: + resolution: {integrity: sha512-sDNkG0umxAgks14sCgumg9PoLkHXszS+T4RGMgFtlRYuXgM4OBZB3EYTa99www5Z4TaUM+Y/rvECn3FKv8Zo4Q==} dependencies: - '@antv/g-lite': 2.0.4 + '@antv/g-lite': 2.0.5 '@antv/util': 3.3.7 tslib: 2.6.2 @@ -1174,8 +1174,8 @@ packages: '@antv/component': 2.0.0 '@antv/coord': 0.4.7 '@antv/event-emitter': 0.1.3 - '@antv/g': 6.0.5 - '@antv/g-canvas': 2.0.4 + '@antv/g': 6.0.6 + '@antv/g-canvas': 2.0.6 '@antv/g-plugin-dragndrop': 2.0.3 '@antv/path-util': 3.0.1 '@antv/scale': 0.4.15 @@ -1201,8 +1201,8 @@ packages: '@antv/component': 2.0.0 '@antv/coord': 0.4.7 '@antv/event-emitter': 0.1.3 - '@antv/g': 6.0.5 - '@antv/g-canvas': 2.0.4 + '@antv/g': 6.0.6 + '@antv/g-canvas': 2.0.6 '@antv/g-plugin-dragndrop': 2.0.3 '@antv/path-util': 3.0.1 '@antv/scale': 0.4.15 @@ -1231,13 +1231,13 @@ packages: '@antv/g-web-animations-api': 2.0.4 dev: false - /@antv/g@6.0.5: - resolution: {integrity: sha512-qj9gszJH+or/sQvf6xCxwUAqJbaqKY1ZOm7OX1csyxGqRqmjn8uhMVCBJxoknOvlhWvMtqXBWDCcTdNs7IJBIQ==} + /@antv/g@6.0.6: + resolution: {integrity: sha512-LWXhBcgjYC/bcLVtNOwrToPosNVsL4ZErj5QexqXDMhqKg2fHMICeJt+1qV2+0bv1sM/vMfeTDRizRxg4OJ3zg==} dependencies: - '@antv/g-camera-api': 2.0.4 - '@antv/g-dom-mutation-observer-api': 2.0.4 - '@antv/g-lite': 2.0.4 - '@antv/g-web-animations-api': 2.0.5 + '@antv/g-camera-api': 2.0.5 + '@antv/g-dom-mutation-observer-api': 2.0.5 + '@antv/g-lite': 2.0.5 + '@antv/g-web-animations-api': 2.0.6 /@antv/path-util@3.0.1: resolution: {integrity: sha512-tpvAzMpF9Qm6ik2YSMqICNU5tco5POOW7S4XoxZAI/B0L26adU+Md/SmO0BBo2SpuywKvzPH3hPT3xmoyhr04Q==}