From e72d05326c3db5d20aa88eec947549a78014b030 Mon Sep 17 00:00:00 2001 From: Wenjun Xu <906626481@qq.com> Date: Mon, 3 Jun 2024 10:55:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E8=A1=8C=E5=88=97?= =?UTF-8?q?=E5=86=BB=E7=BB=93=E8=83=BD=E5=8A=9B=20(#2706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 将 frozen groups 收敛到 frozen facet 中 * refactor: 移除 frozen cell 相关的特殊坐标计算,统一采用 translate 做偏移处理 * refactor: 简化 table-col 冻结处理 * feat: 增加透视表对 trailingRow 冻结的支持 * feat: 完善 splitLine 逻辑 * feat: 处理行头列头滚动布局和 resize 布局 * feat: 处理明细表 data cell resize 绘制 * feat: 更新 playground * test: 单测修复 * fix: 修复列头 resize 布局偏移问题 * test: 单测修复 * fix: 修复方向键移动选中逻辑 * test: 单测修复 * test: data brush selection 单测修复 * fix: 调整明细表 rowOffsets 计算时机 * style: 修复 lint 问题 * docs: 文档更新 * feat: 更新 playground * test: 单测 snapshot 更新 * Update s2-site/examples/layout/frozen/demo/frozen-pivot-tree.ts Co-authored-by: Jinke Li * refactor: 重命名 frozen 变量名 * refactor: 将 calculateRowOffsets 替换为抽象方法 * refactor: 添加方法注释 * chore: 合并 next 代码 * refactor: row-cell 抽取工具函数 * test: 增加透视表行冻结单测 * fix: 在 resize 时加上 preventDefault 事件,兼容 chrome 125 事件更新的处理 (#2739) * refactor: 移除对 frozen options 从 top 到 leaf 的转换 * feat: 增加透视表列头冻结能力 * feat: 增加透视表序号列冻结的能力 * fix: 修复 centerFrame line shadow 显示问题 * fix: 修复类型问题 * refactor: series number cell 继承自 row cell * fix: 修复 lint 问题 * test: 单测修复 * test: 增加透视表列头冻结单测 * docs: 冻结文档更新 * test: snapshot 更新 * fix: s2Options 冻结类型更新 * refactor: rename S2TableSheetFrozenOptions to S2BaseFrozenOptions --------- Co-authored-by: Jinke Li --- .../s2-core/__tests__/bugs/issue-2684-spec.ts | 4 +- .../s2-core/__tests__/bugs/issue-2707-spec.ts | 4 + .../spread-sheet-frozen-spec.ts.snap | 603 ++++++++++++++++++ .../spreadsheet/spread-sheet-frozen-spec.ts | 529 +++++++++++++++ .../spreadsheet/spread-sheet-spec.ts | 1 - .../__tests__/spreadsheet/table-sheet-spec.ts | 9 +- .../__tests__/unit/cell/col-cell-spec.ts | 35 + .../__tests__/unit/cell/header-cell-spec.ts | 81 ++- .../__tests__/unit/cell/merged-cell-spec.ts | 2 +- .../unit/dataset/table-dataset-spec.ts | 11 + .../__snapshots__/table-facet-spec.ts.snap | 10 +- .../unit/facet/bbox/panel-bbox-spec.ts | 29 +- .../unit/facet/header/frozen-row-spec.ts | 13 +- .../__tests__/unit/facet/pivot-facet-spec.ts | 21 +- .../__tests__/unit/facet/table-facet-spec.ts | 36 +- .../data-brush-selection-spec.ts | 83 ++- .../interaction/selected-cell-move-spec.ts | 38 +- .../unit/sheet-type/pivot-sheet-spec.ts | 5 +- .../unit/utils/condition/condition-spec.ts | 6 +- .../unit/utils/data-set-operate-spec.ts | 2 +- .../__tests__/unit/utils/export/copy-spec.ts | 10 +- .../unit/utils/export/export-table-spec.ts | 6 +- .../__tests__/unit/utils/frozen-util-spec.ts | 58 +- .../__tests__/unit/utils/indexes-spec.ts | 14 +- packages/s2-core/__tests__/util/helpers.ts | 56 +- packages/s2-core/src/cell/col-cell.ts | 61 +- packages/s2-core/src/cell/data-cell.ts | 11 +- packages/s2-core/src/cell/row-cell.ts | 84 ++- .../s2-core/src/cell/series-number-cell.ts | 49 +- packages/s2-core/src/cell/table-col-cell.ts | 69 +- packages/s2-core/src/cell/table-data-cell.ts | 136 ++-- packages/s2-core/src/common/constant/basic.ts | 47 +- .../s2-core/src/common/constant/frozen.ts | 39 +- .../s2-core/src/common/constant/options.ts | 14 +- .../s2-core/src/common/interface/frozen.ts | 14 + .../s2-core/src/common/interface/s2Options.ts | 24 +- packages/s2-core/src/facet/base-facet.ts | 49 +- packages/s2-core/src/facet/bbox/panel-bbox.ts | 17 - packages/s2-core/src/facet/frozen-facet.ts | 553 ++++++++-------- packages/s2-core/src/facet/header/base.ts | 47 +- packages/s2-core/src/facet/header/col.ts | 194 ++++-- packages/s2-core/src/facet/header/corner.ts | 35 +- packages/s2-core/src/facet/header/frame.ts | 10 +- packages/s2-core/src/facet/header/row.ts | 164 +++-- .../s2-core/src/facet/header/series-number.ts | 119 ++-- .../s2-core/src/facet/header/table-col.ts | 161 +---- packages/s2-core/src/facet/header/util.ts | 347 ++++++++++ packages/s2-core/src/facet/layout/node.ts | 19 +- packages/s2-core/src/facet/pivot-facet.ts | 115 +--- packages/s2-core/src/facet/table-facet.ts | 70 +- packages/s2-core/src/facet/utils.ts | 135 +--- .../brush-selection/base-brush-selection.ts | 70 +- .../src/interaction/row-column-resize.ts | 2 + .../src/interaction/selected-cell-move.ts | 113 ++-- .../s2-core/src/sheet-type/pivot-sheet.ts | 4 - .../s2-core/src/sheet-type/spread-sheet.ts | 2 - .../s2-core/src/sheet-type/table-sheet.ts | 16 - .../s2-core/src/utils/condition/condition.ts | 4 +- packages/s2-core/src/utils/indexes.ts | 7 +- .../s2-core/src/utils/interaction/scroll.ts | 21 +- packages/s2-core/src/utils/layout/frozen.ts | 41 +- packages/s2-react/playground/config.tsx | 18 +- packages/s2-react/playground/index.tsx | 91 ++- s2-site/docs/api/basic-class/node.zh.md | 1 + s2-site/docs/common/frozen.zh.md | 15 +- .../manual/basic/sheet-type/pivot-mode.zh.md | 55 +- s2-site/docs/manual/migration-v2.zh.md | 31 +- .../layout/frozen/demo/frozen-pivot-grid.ts | 13 +- .../layout/frozen/demo/frozen-pivot-series.ts | 70 ++ .../layout/frozen/demo/frozen-pivot-tree.ts | 13 +- s2-site/examples/layout/frozen/demo/meta.json | 21 +- 71 files changed, 3180 insertions(+), 1677 deletions(-) create mode 100644 packages/s2-core/__tests__/spreadsheet/__snapshots__/spread-sheet-frozen-spec.ts.snap create mode 100644 packages/s2-core/__tests__/spreadsheet/spread-sheet-frozen-spec.ts create mode 100644 packages/s2-core/src/common/interface/frozen.ts create mode 100644 s2-site/examples/layout/frozen/demo/frozen-pivot-series.ts diff --git a/packages/s2-core/__tests__/bugs/issue-2684-spec.ts b/packages/s2-core/__tests__/bugs/issue-2684-spec.ts index de9a75df24..c2c561a93d 100644 --- a/packages/s2-core/__tests__/bugs/issue-2684-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-2684-spec.ts @@ -9,7 +9,7 @@ import { InteractionStateName } from '../../src'; import { getSelectedData } from '../../src/utils/export/copy'; import * as mockDataConfig from '../data/data-issue-2684.json'; import { createPivotSheet } from '../util/helpers'; -import type { S2Options, SpreadSheet } from '@/index'; +import type { CellMeta, S2Options, SpreadSheet } from '@/index'; const s2Options: S2Options = { width: 600, @@ -48,7 +48,7 @@ describe('PivotSheet Special Dimension Values Copy Tests', () => { : String(rowLeafNodes[meta.rowIndex].id); return { ...meta, colId, rowId }; - }); + }) as unknown as CellMeta[]; s2.interaction.changeState({ cells, diff --git a/packages/s2-core/__tests__/bugs/issue-2707-spec.ts b/packages/s2-core/__tests__/bugs/issue-2707-spec.ts index ef5a3271b6..c07e6229ad 100644 --- a/packages/s2-core/__tests__/bugs/issue-2707-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-2707-spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ /** * 明细表数据含有空值导致排序失效 * @description spec for issue 2707 @@ -29,6 +30,7 @@ describe('PivotSheet Special Dimension Values Copy Tests', () => { }); await s2.render(); + // @ts-ignore expect(s2.dataSet.displayData).toMatchSnapshot(); }); @@ -38,6 +40,7 @@ describe('PivotSheet Special Dimension Values Copy Tests', () => { }); await s2.render(); + // @ts-ignore expect(s2.dataSet.displayData).toMatchSnapshot(); }); @@ -59,6 +62,7 @@ describe('PivotSheet Special Dimension Values Copy Tests', () => { }); await s2.render(); + // @ts-ignore expect(s2.dataSet.displayData).toMatchSnapshot(); }); }); diff --git a/packages/s2-core/__tests__/spreadsheet/__snapshots__/spread-sheet-frozen-spec.ts.snap b/packages/s2-core/__tests__/spreadsheet/__snapshots__/spread-sheet-frozen-spec.ts.snap new file mode 100644 index 0000000000..7ffa1cf4bc --- /dev/null +++ b/packages/s2-core/__tests__/spreadsheet/__snapshots__/spread-sheet-frozen-spec.ts.snap @@ -0,0 +1,603 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Spread Sheet Frozen Tests custom tree mode should render correct frozen areas for custom grid mode 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]a-1[&]a-1-1[&]measure-1", + "width": 99.66, + "x": 199.32, + "y": 0, + }, + Object { + "height": 30, + "id": "root[&]a-1[&]a-1-1[&]a-1-1-1", + "width": 99.66, + "x": 199.32, + "y": 30, + }, + Object { + "height": 60, + "id": "root[&]a-1[&]a-1-1", + "width": 99.66, + "x": 99.66, + "y": 0, + }, + Object { + "height": 60, + "id": "root[&]a-1", + "width": 99.66, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests custom tree mode should render correct frozen areas for custom grid mode 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]a-3[&]measure-5", + "width": 199.32, + "x": 99.66, + "y": 240, + }, + Object { + "height": 30, + "id": "root[&]a-3", + "width": 99.66, + "x": 0, + "y": 240, + }, +] +`; + +exports[`Spread Sheet Frozen Tests custom tree mode should render correct frozen areas for custom grid mode with pagination 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]a-2[&]a-2-1", + "width": 199.32, + "x": 99.66, + "y": 120, + }, + Object { + "height": 30, + "id": "root[&]a-2[&]a-2-2[&]measure-3", + "width": 99.66, + "x": 199.32, + "y": 150, + }, + Object { + "height": 60, + "id": "root[&]a-2", + "width": 99.66, + "x": 0, + "y": 120, + }, + Object { + "height": 30, + "id": "root[&]a-2[&]a-2-2", + "width": 99.66, + "x": 99.66, + "y": 150, + }, +] +`; + +exports[`Spread Sheet Frozen Tests custom tree mode should render correct frozen areas for custom grid mode with pagination 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]a-3[&]a-3-2[&]measure-4", + "width": 99.66, + "x": 199.32, + "y": 210, + }, + Object { + "height": 30, + "id": "root[&]a-3[&]a-3-2", + "width": 99.66, + "x": 99.66, + "y": 210, + }, + Object { + "height": 30, + "id": "root[&]a-3", + "width": 99.66, + "x": 0, + "y": 210, + }, +] +`; + +exports[`Spread Sheet Frozen Tests custom tree mode should render correct frozen areas for custom tree mode 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]a-1", + "width": 271.03125, + "x": 0, + "y": 0, + }, + Object { + "height": 30, + "id": "root[&]a-1[&]a-1-1", + "width": 271.03125, + "x": 0, + "y": 30, + }, + Object { + "height": 30, + "id": "root[&]a-1", + "width": 271.03125, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests custom tree mode should render correct frozen areas for custom tree mode 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]a-3[&]measure-5", + "width": 271.03125, + "x": 0, + "y": 420, + }, + Object { + "height": -90, + "id": "root[&]a-3", + "width": 271.03125, + "x": 0, + "y": 420, + }, +] +`; + +exports[`Spread Sheet Frozen Tests custom tree mode should render correct series frozen areas 1`] = ` +Array [ + Object { + "height": 60, + "id": "1", + "width": 80, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests custom tree mode should render correct series frozen areas 2`] = ` +Array [ + Object { + "height": 30, + "id": "3", + "width": 80, + "x": 0, + "y": 420, + }, +] +`; + +exports[`Spread Sheet Frozen Tests grid mode should render correct frozen areas when has no vertical scroll area 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]浙江省[&]杭州市", + "width": 96, + "x": 96, + "y": 0, + }, + Object { + "height": 30, + "id": "root[&]浙江省[&]绍兴市", + "width": 96, + "x": 96, + "y": 30, + }, + Object { + "height": 60, + "id": "root[&]浙江省", + "width": 96, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests grid mode should render correct frozen areas when has no vertical scroll area 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]四川省[&]乐山市", + "width": 96, + "x": 96, + "y": 210, + }, + Object { + "height": 30, + "id": "root[&]四川省", + "width": 96, + "x": 0, + "y": 210, + }, +] +`; + +exports[`Spread Sheet Frozen Tests grid mode should render correct frozen areas when has vertical scroll area 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]浙江省[&]杭州市", + "width": 96, + "x": 96, + "y": 0, + }, + Object { + "height": 30, + "id": "root[&]浙江省[&]绍兴市", + "width": 96, + "x": 96, + "y": 30, + }, + Object { + "height": 60, + "id": "root[&]浙江省", + "width": 96, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests grid mode should render correct frozen areas when has vertical scroll area 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]四川省[&]乐山市", + "width": 96, + "x": 96, + "y": 210, + }, + Object { + "height": 30, + "id": "root[&]四川省", + "width": 96, + "x": 0, + "y": 210, + }, +] +`; + +exports[`Spread Sheet Frozen Tests grid mode should render correct frozen areas with pagination 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]四川省[&]成都市", + "width": 96, + "x": 96, + "y": 120, + }, + Object { + "height": 30, + "id": "root[&]四川省[&]绵阳市", + "width": 96, + "x": 96, + "y": 150, + }, + Object { + "height": 60, + "id": "root[&]四川省", + "width": 96, + "x": 0, + "y": 120, + }, +] +`; + +exports[`Spread Sheet Frozen Tests grid mode should render correct frozen areas with pagination 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]四川省[&]乐山市", + "width": 96, + "x": 96, + "y": 210, + }, + Object { + "height": 30, + "id": "root[&]四川省", + "width": 96, + "x": 0, + "y": 210, + }, +] +`; + +exports[`Spread Sheet Frozen Tests grid mode should render correct series frozen areas 1`] = ` +Array [ + Object { + "height": 60, + "id": "1", + "width": 80, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests grid mode should render correct series frozen areas 2`] = ` +Array [ + Object { + "height": 30, + "id": "2", + "width": 80, + "x": 0, + "y": 210, + }, +] +`; + +exports[`Spread Sheet Frozen Tests pivot col header frozen should render correct frozen areas for row header frozen 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]家具[&]桌子[&]number", + "width": 100, + "x": 0, + "y": 60, + }, + Object { + "height": 30, + "id": "root[&]家具[&]桌子", + "width": 100, + "x": 0, + "y": 30, + }, + Object { + "height": 30, + "id": "root[&]家具", + "width": 100, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests pivot col header frozen should render correct frozen areas for row header frozen 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]办公用品[&]纸张[&]number", + "width": 100, + "x": 900, + "y": 60, + }, + Object { + "height": 30, + "id": "root[&]办公用品[&]纸张", + "width": 100, + "x": 900, + "y": 30, + }, + Object { + "height": 30, + "id": "root[&]办公用品", + "width": 100, + "x": 900, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests pivot col header frozen should render correct frozen areas for row header is not frozen 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]家具[&]桌子[&]number", + "width": 100, + "x": 0, + "y": 60, + }, + Object { + "height": 30, + "id": "root[&]家具[&]桌子", + "width": 100, + "x": 0, + "y": 30, + }, + Object { + "height": 30, + "id": "root[&]家具", + "width": 100, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests pivot col header frozen should render correct frozen areas for row header is not frozen 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]办公用品[&]纸张[&]number", + "width": 100, + "x": 900, + "y": 60, + }, + Object { + "height": 30, + "id": "root[&]办公用品[&]纸张", + "width": 100, + "x": 900, + "y": 30, + }, + Object { + "height": 30, + "id": "root[&]办公用品", + "width": 100, + "x": 900, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests tree mode should render correct frozen areas when has no vertical scroll area 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]浙江省", + "width": 120, + "x": 0, + "y": 0, + }, + Object { + "height": 30, + "id": "root[&]浙江省[&]杭州市", + "width": 120, + "x": 0, + "y": 30, + }, + Object { + "height": 30, + "id": "root[&]浙江省", + "width": 120, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests tree mode should render correct frozen areas when has no vertical scroll area 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]四川省[&]乐山市", + "width": 120, + "x": 0, + "y": 270, + }, + Object { + "height": -90, + "id": "root[&]四川省", + "width": 120, + "x": 0, + "y": 270, + }, +] +`; + +exports[`Spread Sheet Frozen Tests tree mode should render correct frozen areas when has vertical scroll area 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]浙江省", + "width": 120, + "x": 0, + "y": 0, + }, + Object { + "height": 30, + "id": "root[&]浙江省[&]杭州市", + "width": 120, + "x": 0, + "y": 30, + }, + Object { + "height": 30, + "id": "root[&]浙江省", + "width": 120, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests tree mode should render correct frozen areas when has vertical scroll area 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]四川省[&]乐山市", + "width": 120, + "x": 0, + "y": 270, + }, + Object { + "height": -90, + "id": "root[&]四川省", + "width": 120, + "x": 0, + "y": 270, + }, +] +`; + +exports[`Spread Sheet Frozen Tests tree mode should render correct frozen areas with pagination 1`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]浙江省[&]舟山市", + "width": 120, + "x": 0, + "y": 120, + }, + Object { + "height": 30, + "id": "root[&]四川省", + "width": 120, + "x": 0, + "y": 150, + }, + Object { + "height": -90, + "id": "root[&]浙江省", + "width": 120, + "x": 0, + "y": 120, + }, +] +`; + +exports[`Spread Sheet Frozen Tests tree mode should render correct frozen areas with pagination 2`] = ` +Array [ + Object { + "height": 30, + "id": "root[&]四川省[&]绵阳市", + "width": 120, + "x": 0, + "y": 210, + }, + Object { + "height": -30, + "id": "root[&]四川省", + "width": 120, + "x": 0, + "y": 210, + }, +] +`; + +exports[`Spread Sheet Frozen Tests tree mode should render correct series frozen areas 1`] = ` +Array [ + Object { + "height": 60, + "id": "1", + "width": 80, + "x": 0, + "y": 0, + }, +] +`; + +exports[`Spread Sheet Frozen Tests tree mode should render correct series frozen areas 2`] = ` +Array [ + Object { + "height": 30, + "id": "2", + "width": 80, + "x": 0, + "y": 270, + }, +] +`; diff --git a/packages/s2-core/__tests__/spreadsheet/spread-sheet-frozen-spec.ts b/packages/s2-core/__tests__/spreadsheet/spread-sheet-frozen-spec.ts new file mode 100644 index 0000000000..57a5eb3932 --- /dev/null +++ b/packages/s2-core/__tests__/spreadsheet/spread-sheet-frozen-spec.ts @@ -0,0 +1,529 @@ +import * as mockDataConfig from 'tests/data/mock-dataset.json'; +import { getContainer } from 'tests/util/helpers'; +import { map, merge } from 'lodash'; +import { CustomGridData } from 'tests/data/data-custom-grid'; +import { customRowGridFields } from 'tests/data/custom-grid-fields'; +import { + type FrozenFacet, + PivotSheet, + type S2DataConfig, + type S2Options, +} from '../../src'; +import { pickMap } from '../util/fp'; + +function expectFrozenGroup( + s2: PivotSheet, + headerName: 'rowHeader' | 'columnHeader' | 'seriesNumberHeader' = 'rowHeader', +) { + const pickCoordinate = pickMap(['id', 'x', 'y', 'width', 'height']); + + const actualHead = pickCoordinate( + map(s2.facet[headerName]?.frozenGroup.children, 'meta'), + ); + + expect(actualHead).toMatchSnapshot(); + + const actualTrailing = pickCoordinate( + map(s2.facet[headerName]?.frozenTrailingGroup.children, 'meta'), + ); + + expect(actualTrailing).toMatchSnapshot(); +} + +function getFrozenGroupPosition( + s2: PivotSheet, + headerName: 'rowHeader' | 'columnHeader' = 'rowHeader', +) { + return [ + s2.facet[headerName]?.frozenGroup.getPosition().map(Math.floor), + s2.facet[headerName]?.frozenTrailingGroup.getPosition().map(Math.floor), + ]; +} + +describe('Spread Sheet Frozen Tests', () => { + let container: HTMLElement; + + beforeEach(() => { + container = getContainer(); + }); + + afterEach(() => { + container?.remove(); + }); + + describe('grid mode', () => { + test('should render correct frozen areas when has vertical scroll area', async () => { + const s2 = new PivotSheet(container, mockDataConfig, { + width: 300, + height: 300, + hierarchyType: 'grid', + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenRow: { + height: 60, + y: 0, + range: [0, 1], + }, + frozenTrailingRow: { + height: 30, + y: 210, + range: [7, 7], + }, + }); + + expectFrozenGroup(s2); + s2.destroy(); + }); + + test('should render correct frozen areas when has no vertical scroll area', async () => { + const s2 = new PivotSheet(container, mockDataConfig, { + width: 300, + height: 800, + hierarchyType: 'grid', + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenRow: { + height: 60, + y: 0, + range: [0, 1], + }, + frozenTrailingRow: { + height: 30, + y: 210, + range: [7, 7], + }, + }); + + expectFrozenGroup(s2); + s2.destroy(); + }); + + test('should render correct frozen areas with pagination', async () => { + const s2 = new PivotSheet(container, mockDataConfig, { + width: 300, + height: 300, + hierarchyType: 'grid', + pagination: { + current: 2, + pageSize: 4, + }, + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenRow: { + height: 60, + y: 120, + range: [4, 5], + }, + frozenTrailingRow: { + height: 30, + y: 210, + range: [7, 7], + }, + }); + expectFrozenGroup(s2); + s2.destroy(); + }); + + test('should render correct series frozen areas', async () => { + const s2 = new PivotSheet(container, mockDataConfig, { + width: 300, + height: 300, + hierarchyType: 'grid', + seriesNumber: { + enable: true, + }, + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expectFrozenGroup(s2, 'seriesNumberHeader'); + + s2.destroy(); + }); + }); + + describe('tree mode', () => { + test('should render correct frozen areas when has vertical scroll area', async () => { + const s2 = new PivotSheet(container, mockDataConfig, { + width: 300, + height: 300, + hierarchyType: 'tree', + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenRow: { + height: 60, + y: 0, + range: [0, 1], + }, + frozenTrailingRow: { + height: 30, + y: 270, + range: [9, 9], + }, + }); + + expectFrozenGroup(s2); + s2.destroy(); + }); + + test('should render correct frozen areas when has no vertical scroll area', async () => { + const s2 = new PivotSheet(container, mockDataConfig, { + width: 300, + height: 800, + hierarchyType: 'tree', + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenRow: { + height: 60, + y: 0, + range: [0, 1], + }, + frozenTrailingRow: { + height: 30, + y: 270, + range: [9, 9], + }, + }); + + expectFrozenGroup(s2); + s2.destroy(); + }); + + test('should render correct frozen areas with pagination', async () => { + const s2 = new PivotSheet(container, mockDataConfig, { + width: 300, + height: 300, + hierarchyType: 'tree', + pagination: { + current: 2, + pageSize: 4, + }, + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenRow: { + height: 60, + y: 120, + range: [4, 5], + }, + frozenTrailingRow: { + height: 30, + y: 210, + range: [7, 7], + }, + }); + expectFrozenGroup(s2); + s2.destroy(); + }); + + test('should render correct series frozen areas', async () => { + const s2 = new PivotSheet(container, mockDataConfig, { + width: 300, + height: 300, + hierarchyType: 'tree', + seriesNumber: { + enable: true, + }, + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expectFrozenGroup(s2, 'seriesNumberHeader'); + + s2.destroy(); + }); + }); + + describe('custom tree mode', () => { + const pivotSheetCustomRowGridDataCfg: S2DataConfig = { + data: CustomGridData, + meta: [ + ...mockDataConfig.meta, + { + field: 'a-1', + name: '层级1', + }, + { + field: 'a-1-1', + name: '层级2', + }, + { + field: 'measure-1', + name: '层级3', + }, + { + field: 'measure-1', + formatter: (value) => `#-${value}`, + }, + ], + fields: customRowGridFields, + }; + + test('should render correct frozen areas for custom grid mode', async () => { + const s2 = new PivotSheet(container, pivotSheetCustomRowGridDataCfg, { + width: 600, + height: 400, + hierarchyType: 'grid', + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenRow: { + height: 60, + y: 0, + range: [0, 1], + }, + frozenTrailingRow: { + height: 30, + y: 240, + range: [8, 8], + }, + }); + + expectFrozenGroup(s2); + s2.destroy(); + }); + + test('should render correct frozen areas for custom tree mode', async () => { + const s2 = new PivotSheet(container, pivotSheetCustomRowGridDataCfg, { + width: 600, + height: 400, + hierarchyType: 'tree', + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenRow: { + height: 60, + y: 0, + range: [0, 1], + }, + frozenTrailingRow: { + height: 30, + y: 420, + range: [14, 14], + }, + }); + + expectFrozenGroup(s2); + s2.destroy(); + }); + + test('should render correct frozen areas for custom grid mode with pagination', async () => { + const s2 = new PivotSheet(container, pivotSheetCustomRowGridDataCfg, { + width: 600, + height: 400, + hierarchyType: 'grid', + pagination: { + current: 2, + pageSize: 4, + }, + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenRow: { + height: 60, + y: 120, + range: [4, 5], + }, + frozenTrailingRow: { + height: 30, + y: 210, + range: [7, 7], + }, + }); + + expectFrozenGroup(s2); + s2.destroy(); + }); + + test('should render correct series frozen areas', async () => { + const s2 = new PivotSheet(container, pivotSheetCustomRowGridDataCfg, { + width: 300, + height: 300, + hierarchyType: 'tree', + seriesNumber: { + enable: true, + }, + frozen: { + rowCount: 2, + trailingRowCount: 1, + }, + }); + + await s2.render(); + + expectFrozenGroup(s2, 'seriesNumberHeader'); + + s2.destroy(); + }); + }); + + describe('pivot col header frozen', () => { + const baseOptions: S2Options = { + width: 600, + height: 400, + frozen: { + colCount: 1, + trailingColCount: 1, + }, + style: { + colCell: { + width: 100, + widthByField: { + 'root[&]家具[&]沙发[&]number': 400, + 'root[&]办公用品[&]笔[&]number': 400, + }, + }, + }, + }; + + test('should render correct frozen areas for row header frozen', async () => { + const s2 = new PivotSheet(container, mockDataConfig, baseOptions); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenCol: { + width: 100, + x: 0, + range: [0, 0], + }, + frozenTrailingCol: { + width: 100, + x: 900, + range: [3, 3], + }, + }); + + expectFrozenGroup(s2, 'columnHeader'); + + const prev = getFrozenGroupPosition(s2, 'columnHeader'); + + s2.updateScrollOffset({ offsetX: { value: 100, animate: false } }); + + // 移动后,frozen col 和 trailing col 的位置都不变 + expect(getFrozenGroupPosition(s2, 'columnHeader')).toEqual(prev); + + s2.destroy(); + }); + + test('should render correct frozen areas for row header is not frozen', async () => { + const s2 = new PivotSheet( + container, + mockDataConfig, + merge({}, baseOptions, { + frozen: { + rowHeader: false, + }, + }), + ); + + await s2.render(); + + expect((s2.facet as FrozenFacet).frozenGroupAreas).toMatchObject({ + frozenCol: { + width: 100, + x: 0, + range: [0, 0], + }, + frozenTrailingCol: { + width: 100, + x: 900, + range: [3, 3], + }, + }); + + expectFrozenGroup(s2, 'columnHeader'); + let prev = getFrozenGroupPosition(s2, 'columnHeader'); + + s2.updateScrollOffset({ offsetX: { value: 100, animate: false } }); + // 移动后,frozen col 会改变 而 trailing col 的位置不变 + let current = getFrozenGroupPosition(s2, 'columnHeader'); + + expect(current[0]?.[0]).toEqual(prev[0]?.[0]! - 100); + expect(current[1]).toEqual(prev[1]); + + // 移动超过角头宽度 + // 移动后,frozen col 和 trailing col 的位置都不变 + s2.updateScrollOffset({ offsetX: { value: 300, animate: false } }); + + prev = getFrozenGroupPosition(s2, 'columnHeader'); + s2.updateScrollOffset({ offsetX: { value: 300, animate: false } }); + current = getFrozenGroupPosition(s2, 'columnHeader'); + + expect(current).toEqual(prev); + expect(current).toEqual([ + [2, 0, 0], + [-400, 0, 0], + ]); + + s2.destroy(); + }); + }); +}); diff --git a/packages/s2-core/__tests__/spreadsheet/spread-sheet-spec.ts b/packages/s2-core/__tests__/spreadsheet/spread-sheet-spec.ts index c9113ec0cc..f81b5d606b 100644 --- a/packages/s2-core/__tests__/spreadsheet/spread-sheet-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/spread-sheet-spec.ts @@ -568,7 +568,6 @@ describe('SpreadSheet Tests', () => { s2.setOptions({ hierarchyType: 'tree', tooltip: { enable: true }, - frozen: { firstRow: true }, }); s2.setDataCfg({ diff --git a/packages/s2-core/__tests__/spreadsheet/table-sheet-spec.ts b/packages/s2-core/__tests__/spreadsheet/table-sheet-spec.ts index 800a55359e..d758d29d69 100644 --- a/packages/s2-core/__tests__/spreadsheet/table-sheet-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/table-sheet-spec.ts @@ -1,7 +1,7 @@ /* eslint-disable jest/expect-expect */ import { getContainer, getMockData, sleep } from 'tests/util/helpers'; -import type { SpreadSheet, TableFacet } from '../../esm'; -import { setLang } from '../../src'; +import type { TableColHeader } from '../../src/facet/header/table-col'; +import { SpreadSheet, TableFacet, setLang } from '../../src'; import { DeviceType, ResizeType, @@ -180,7 +180,8 @@ describe('TableSheet normal spec', () => { ); await sleep(500); - const firstColCell = s2.facet.getColNodes()[1].belongsCell! as any; + const firstColCell = (s2.facet.columnHeader as TableColHeader).frozenGroup + .children[1] as any; expect(firstColCell.shouldAddVerticalResizeArea()).toBeTruthy(); expect(firstColCell.getVerticalResizeAreaOffset()).toEqual({ x: 81, y: 0 }); @@ -215,7 +216,7 @@ describe('TableSheet normal spec', () => { await s2.render(); - await sleep(100); + await sleep(1000); let columnNodes = s2.facet.getColNodes(); diff --git a/packages/s2-core/__tests__/unit/cell/col-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/col-cell-spec.ts index 66be515b2d..b1a6e65f93 100644 --- a/packages/s2-core/__tests__/unit/cell/col-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/col-cell-spec.ts @@ -2,6 +2,7 @@ import { set } from 'lodash'; import { createFakeSpreadSheet, createPivotSheet } from 'tests/util/helpers'; import type { ColHeaderConfig } from '../../../src/facet/header'; +import { FrozenGroupArea } from '../../../src/common'; import { getContainer } from './../../util/helpers'; import { ColCell } from '@/cell'; import { EXTRA_FIELD, type Formatter, type TextAlign } from '@/common'; @@ -19,6 +20,31 @@ describe('Col Cell Tests', () => { s2 = new MockPivotSheet(getContainer()); s2.isFrozenRowHeader = () => false; s2.dataSet = new MockPivotDataSet(s2); + + s2.facet = { + frozenGroupAreas: { + [FrozenGroupArea.Col]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingCol]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.Row]: { + height: 0, + y: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingRow]: { + height: 0, + y: 0, + range: [] as number[], + }, + }, + }; }); describe('None-leaf Nodes Tests', () => { @@ -32,8 +58,13 @@ describe('Col Cell Tests', () => { const headerConfig: Partial = { width: 500, // col header width + viewportWidth: 500, cornerWidth: 100, scrollX: 30, // 模拟滚动了 30px + position: { + x: 100, + y: 0, + }, }; const actualTextWidth = 40; // 文字长度 @@ -81,6 +112,10 @@ describe('Col Cell Tests', () => { const headerConfig: Partial = { spreadsheet: createFakeSpreadSheet(), + position: { + x: 0, + y: 0, + }, }; test('should get correct col cell formatter', () => { 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 ec7e9d71e6..0a95b1cbe2 100644 --- a/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/header-cell-spec.ts @@ -1,5 +1,9 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import type { Formatter, HeaderActionIcon } from '@/common'; +import { + FrozenGroupArea, + type Formatter, + type HeaderActionIcon, +} from '@/common'; import { PivotDataSet, TableDataSet } from '@/data-set'; import { PivotSheet, SpreadSheet, TableSheet } from '@/sheet-type'; import { ColCell, CornerCell, RowCell, TableColCell } from '@/cell'; @@ -150,6 +154,31 @@ describe('header cell formatter test', () => { test('should render row header action icons', () => { s2.options.headerActionIcons = rowHeaderActionIcons; + s2.facet = { + // @ts-ignore + frozenGroupAreas: { + [FrozenGroupArea.Col]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingCol]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.Row]: { + height: 0, + y: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingRow]: { + height: 0, + y: 0, + range: [] as number[], + }, + }, + }; const rowCell = new RowCell(rowNode, s2); @@ -163,6 +192,31 @@ describe('header cell formatter test', () => { test('should render col header action icons', () => { s2.options.headerActionIcons = colHeaderActionIcons; + s2.facet = { + // @ts-ignore + frozenGroupAreas: { + [FrozenGroupArea.Col]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingCol]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.Row]: { + height: 0, + y: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingRow]: { + height: 0, + y: 0, + range: [] as number[], + }, + }, + }; const colCell = new ColCell(colNode, s2); @@ -176,6 +230,31 @@ describe('header cell formatter test', () => { test('should render col header sort icons', () => { s2.options.showDefaultHeaderActionIcon = true; + s2.facet = { + // @ts-ignore + frozenGroupAreas: { + [FrozenGroupArea.Col]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingCol]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.Row]: { + height: 0, + y: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingRow]: { + height: 0, + y: 0, + range: [] as number[], + }, + }, + }; jest.spyOn(s2, 'isValueInCols').mockImplementationOnce(() => true); const showSortIconSpy = jest .spyOn(ColCell.prototype, 'showSortIcon') diff --git a/packages/s2-core/__tests__/unit/cell/merged-cell-spec.ts b/packages/s2-core/__tests__/unit/cell/merged-cell-spec.ts index 25699187f3..46a89dc7de 100644 --- a/packages/s2-core/__tests__/unit/cell/merged-cell-spec.ts +++ b/packages/s2-core/__tests__/unit/cell/merged-cell-spec.ts @@ -58,7 +58,7 @@ describe('Merged Cell Tests', () => { rowLeafNodes: [], }, getRowLeafNodeByIndex: () => {}, - } as PivotFacet; + } as unknown as PivotFacet; mergedCell = new MergedCell(s2, cells, meta); }); diff --git a/packages/s2-core/__tests__/unit/dataset/table-dataset-spec.ts b/packages/s2-core/__tests__/unit/dataset/table-dataset-spec.ts index 07cc181f83..1fd0419369 100644 --- a/packages/s2-core/__tests__/unit/dataset/table-dataset-spec.ts +++ b/packages/s2-core/__tests__/unit/dataset/table-dataset-spec.ts @@ -32,6 +32,17 @@ describe('Table Mode Dataset Test', () => { s2.options = { frozen: {}, }; + + s2.facet = { + // @ts-ignore + getFrozenOptions: jest.fn().mockReturnValue({ + colCount: 0, + trailingColCount: 0, + rowCount: 0, + trailingRowCount: 0, + }), + }; + dataSet = new TableDataSet(s2); dataSet.setDataCfg(dataCfg); diff --git a/packages/s2-core/__tests__/unit/facet/__snapshots__/table-facet-spec.ts.snap b/packages/s2-core/__tests__/unit/facet/__snapshots__/table-facet-spec.ts.snap index df5e1dc0ba..3efd8a64c7 100644 --- a/packages/s2-core/__tests__/unit/facet/__snapshots__/table-facet-spec.ts.snap +++ b/packages/s2-core/__tests__/unit/facet/__snapshots__/table-facet-spec.ts.snap @@ -70,7 +70,7 @@ Array [ ] `; -exports[`Table Mode Facet With Column Grouping Frozen Test should get correct frozenInfo 1`] = ` +exports[`Table Mode Facet With Column Grouping Frozen Test should get correct frozen group positions 1`] = ` Object { "frozenCol": Object { "range": Array [ @@ -94,7 +94,7 @@ Object { 2, ], "width": 199.66, - "x": 399.34000000000003, + "x": 399.32, }, "frozenTrailingRow": Object { "height": 60, @@ -102,12 +102,12 @@ Object { 30, 31, ], - "y": 502, + "y": 900, }, } `; -exports[`Table Mode Facet With Frozen Test should get correct frozenInfo 1`] = ` +exports[`Table Mode Facet With Frozen Test should get correct frozen group positions 1`] = ` Object { "frozenCol": Object { "range": Array [ @@ -139,7 +139,7 @@ Object { 30, 31, ], - "y": 502, + "y": 900, }, } `; diff --git a/packages/s2-core/__tests__/unit/facet/bbox/panel-bbox-spec.ts b/packages/s2-core/__tests__/unit/facet/bbox/panel-bbox-spec.ts index e6635f0a66..22177d3494 100644 --- a/packages/s2-core/__tests__/unit/facet/bbox/panel-bbox-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/bbox/panel-bbox-spec.ts @@ -18,7 +18,6 @@ describe('PanelBBox Tests', () => { realWidth: number, realHeight: number, extraOptions: S2Options = {}, - shouldEnableFrozenHeaders = true, ) => ({ layoutResult: { @@ -54,9 +53,6 @@ describe('PanelBBox Tests', () => { isFrozenRowHeader() { return true; }, - enableFrozenHeaders() { - return shouldEnableFrozenHeaders; - }, isPivotMode() { return true; }, @@ -103,7 +99,7 @@ describe('PanelBBox Tests', () => { expect(bbox.originalWidth).toBe(2000); }); - test('should return full viewport when frozen trailing col and row', () => { + test('should return actual viewport when frozen trailing col and row', () => { const facet = getMockFacet(200, 200, { frozen: { trailingColCount: 2, @@ -115,26 +111,21 @@ describe('PanelBBox Tests', () => { expect(bbox.width).toBe(578); expect(bbox.height).toBe(573); - expect(bbox.viewportWidth).toBe(578); - expect(bbox.viewportHeight).toBe(573); - expect(bbox.maxX).toBe(600); - expect(bbox.maxY).toBe(595); + expect(bbox.viewportWidth).toBe(200); + expect(bbox.viewportHeight).toBe(200); + expect(bbox.maxX).toBe(222); + expect(bbox.maxY).toBe(222); expect(bbox.originalHeight).toBe(200); expect(bbox.originalWidth).toBe(200); }); test('should return correct viewport when disable frozen trailing col and row', () => { - const facet = getMockFacet( - 200, - 200, - { - frozen: { - trailingColCount: 2, - trailingRowCount: 2, - }, + const facet = getMockFacet(200, 200, { + frozen: { + trailingColCount: 0, + trailingRowCount: 0, }, - false, - ); + }); const bbox = new PanelBBox(facet, true); diff --git a/packages/s2-core/__tests__/unit/facet/header/frozen-row-spec.ts b/packages/s2-core/__tests__/unit/facet/header/frozen-row-spec.ts index 2e11f10094..b589b0a642 100644 --- a/packages/s2-core/__tests__/unit/facet/header/frozen-row-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/header/frozen-row-spec.ts @@ -2,14 +2,14 @@ import { createPivotSheet } from 'tests/util/helpers'; import { get } from 'lodash'; import type { HierarchyType, RowHeader } from '../../../../src'; import type { FrozenFacet } from '../../../../src/facet/frozen-facet'; -import { DEFAULT_OPTIONS, FrozenGroupType } from '@/common'; +import { DEFAULT_OPTIONS, FrozenGroupArea } from '@/common'; import { RowCell } from '@/cell'; const s2 = createPivotSheet( { ...DEFAULT_OPTIONS, frozen: { - firstRow: true, + rowCount: 1, }, totals: { row: { showGrandTotals: true, reverseGrandTotalsLayout: true } }, seriesNumber: { @@ -29,11 +29,11 @@ describe('Pivot Frozen Row Header Test', () => { const rowHeader = s2.facet.rowHeader as RowHeader; expect(rowHeader).toBeTruthy(); - expect(rowHeader.frozenRowGroup).toBeTruthy(); + expect(rowHeader.frozenGroup).toBeTruthy(); expect(rowHeader.scrollGroup).toBeTruthy(); - expect(rowHeader.frozenRowGroup.children).toHaveLength(1); - const frozenRowCell = rowHeader.frozenRowGroup.children[0]; + expect(rowHeader.frozenGroup.children).toHaveLength(1); + const frozenRowCell = rowHeader.frozenGroup.children[0]; expect(frozenRowCell instanceof RowCell).toBeTrue(); expect(get(frozenRowCell, 'meta.height')).toEqual(30); @@ -45,8 +45,7 @@ describe('Pivot Frozen Row Header Test', () => { expect(get(frozenRowCell, 'meta.height')).toEqual(30); expect( - (s2.facet as FrozenFacet).frozenGroupInfo[FrozenGroupType.FROZEN_ROW] - .height, + (s2.facet as FrozenFacet).frozenGroupAreas[FrozenGroupArea.Row].height, ).toBe(30); }, ); diff --git a/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts b/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts index e4844494e7..2f4106c086 100644 --- a/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts @@ -5,7 +5,7 @@ import { Canvas, Group, Rect, type CanvasConfig } from '@antv/g'; import { Renderer } from '@antv/g-canvas'; import { find, size } from 'lodash'; import { assembleDataCfg, assembleOptions } from 'tests/util'; -import { FrozenGroupType, getDefaultSeriesNumberText } from '../../../src'; +import { getDefaultSeriesNumberText } from '../../../src'; import { createFakeSpreadSheet } from '../../util/helpers'; import { getMockPivotMeta } from './util'; import { CornerCell, DataCell } from '@/cell'; @@ -85,12 +85,7 @@ jest.mock('@/sheet-type', () => { getHiddenColumnsInfo: jest.fn(), getCellMeta: jest.fn().mockRejectedValue({}), getRowLeafNodeByIndex: () => [], - frozenGroupInfo: { - [FrozenGroupType.FROZEN_ROW]: {}, - [FrozenGroupType.FROZEN_COL]: {}, - [FrozenGroupType.FROZEN_TRAILING_ROW]: {}, - [FrozenGroupType.FROZEN_TRAILING_COL]: {}, - }, + getCellRange: jest.fn().mockReturnValue({ start: 0, end: 100 }), cornerBBox: {}, getHeaderNodes: jest.fn().mockReturnValue([]), }, @@ -103,9 +98,6 @@ jest.mock('@/sheet-type', () => { }, measureTextWidth: jest.fn() as unknown as SpreadSheet['measureTextWidth'], - enableFrozenHeaders() { - return true; - }, getSeriesNumberText: jest.fn(() => getDefaultSeriesNumberText()), }; }), @@ -154,6 +146,7 @@ describe('Pivot Mode Facet Test', () => { s2.isCustomHeaderFields = jest.fn(); s2.isCustomColumnFields = jest.fn(); s2.isCustomRowFields = jest.fn(); + s2.isFrozenRowHeader = jest.fn(); s2.options = assembleOptions({ dataCell: (viewMeta) => new DataCell(viewMeta, s2), }); @@ -296,7 +289,7 @@ describe('Pivot Mode Facet Test', () => { expect(rowHeader!.parsedStyle.visibility).not.toEqual('hidden'); expect(cornerHeader).toBeInstanceOf(CornerHeader); - expect(cornerHeader.children).toHaveLength(2); + expect(cornerHeader.scrollGroup.children).toHaveLength(2); expect(cornerHeader.parsedStyle.visibility).not.toEqual('hidden'); expect(columnHeader).toBeInstanceOf(ColHeader); @@ -321,14 +314,16 @@ describe('Pivot Mode Facet Test', () => { s2.dataSet = new MockPivotDataSet(s2); const seriesNumberFacet = new PivotFacet(s2); + s2.facet = seriesNumberFacet; + seriesNumberFacet.render(); const { cornerHeader } = seriesNumberFacet; expect(cornerHeader instanceof CornerHeader).toBeTrue(); - expect(cornerHeader.children).toHaveLength(3); + expect(cornerHeader.scrollGroup.children).toHaveLength(3); expect( - (cornerHeader.children as CornerCell[]).every( + (cornerHeader.scrollGroup.children as CornerCell[]).every( (cell) => cell.getMeta().spreadsheet, ), ).toBeTrue(); 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 dc6e92b703..7d3c0afc2d 100644 --- a/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts @@ -12,7 +12,6 @@ import { LayoutWidthType, ROOT_NODE_ID } from '@/common/constant'; import { Store } from '@/common/store'; import { TableDataSet } from '@/data-set/table-data-set'; import { TableFacet } from '@/facet/table-facet'; -import { getFrozenLeafNodesCount } from '@/facet/utils'; import { Node, customMerge, @@ -76,9 +75,6 @@ jest.mock('@/sheet-type', () => { getState: jest.fn(), getCells: jest.fn(() => []), }, - enableFrozenHeaders() { - return false; - }, isFrozenRowHeader() { return false; }, @@ -358,10 +354,10 @@ describe('Table Mode Facet With Frozen Test', () => { }, }); - test('should get correct frozenInfo', () => { + test('should get correct frozen group positions', () => { facet.calculateFrozenGroupInfo(); - expect(facet.frozenGroupInfo).toMatchSnapshot(); + expect(facet.frozenGroupAreas).toMatchSnapshot(); }); test('should get correct xy indexes with frozen', () => { @@ -415,7 +411,7 @@ describe('Table Mode Facet With Frozen Test', () => { .slice(-trailingRowCount!) .reverse() .map((_, idx) => facet.getCellMeta(displayData.length - 1 - idx, 1)!.y), - ).toEqual([532, 502]); + ).toEqual([930, 900]); }); test('should get correct viewCellHeights result', () => { @@ -652,9 +648,9 @@ describe('Table Mode Facet With Column Grouping Frozen Test', () => { }, ); - test('should get correct frozenInfo', () => { + test('should get correct frozen group positions', () => { facet.calculateFrozenGroupInfo(); - expect(facet.frozenGroupInfo).toMatchSnapshot(); + expect(facet.frozenGroupAreas).toMatchSnapshot(); }); test('should get correct col layout with frozen col', () => { @@ -668,26 +664,6 @@ describe('Table Mode Facet With Column Grouping Frozen Test', () => { ).toStrictEqual([0]); }); - test('should get correct cell layout with frozenTrailingCol', () => { - const { trailingColCount: frozenTrailingColCount } = s2.options.frozen!; - const topLevelNodes = facet - .getColNodes() - .filter((node) => node.parent!.id === ROOT_NODE_ID); - const { trailingColCount } = getFrozenLeafNodesCount( - topLevelNodes, - 0, - frozenTrailingColCount!, - ); - - expect( - facet - .getColLeafNodes() - .slice(-trailingColCount) - .reverse() - .map((node) => Math.floor(node.x)), - ).toEqual([399]); - }); - test('should get correct cell layout with frozenTrailingRow', () => { const { trailingRowCount } = s2.options.frozen!; const displayData = s2.dataSet.getDisplayDataSet(); @@ -697,7 +673,7 @@ describe('Table Mode Facet With Column Grouping Frozen Test', () => { .slice(-trailingRowCount!) .reverse() .map((_, idx) => facet.getCellMeta(displayData.length - 1 - idx, 1)!.y), - ).toEqual([532, 502]); + ).toEqual([930, 900]); }); test('should get correct viewCellHeights result', () => { diff --git a/packages/s2-core/__tests__/unit/interaction/brush-selection/data-brush-selection-spec.ts b/packages/s2-core/__tests__/unit/interaction/brush-selection/data-brush-selection-spec.ts index 017d8f5e60..82449732eb 100644 --- a/packages/s2-core/__tests__/unit/interaction/brush-selection/data-brush-selection-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/brush-selection/data-brush-selection-spec.ts @@ -6,7 +6,6 @@ import type { TableFacet } from '@/facet'; import { CellType, DataCellBrushSelection, - FrozenGroupType, InteractionBrushSelectionStage, Node, PivotSheet, @@ -21,6 +20,7 @@ import { type S2Options, type ViewMeta, EventController, + FrozenGroupArea, } from '@/index'; import { RootInteraction } from '@/interaction/root'; @@ -451,6 +451,12 @@ describe('Interaction Data Cell Brush Selection Tests', () => { return {}; }); (s2.facet as TableFacet).panelScrollGroupIndexes = [1, 8, 1, 8]; + (s2.facet as TableFacet).getFrozenOptions = jest.fn().mockReturnValue({ + colCount: 1, + rowCount: 1, + trailingColCount: 1, + trailingRowCount: 1, + }); expect(adjustNextColIndexWithFrozen(9, ScrollDirection.SCROLL_DOWN)).toBe( 8, @@ -473,35 +479,42 @@ describe('Interaction Data Cell Brush Selection Tests', () => { const { facet } = s2; expect(getScrollOffsetForCol(7, ScrollDirection.SCROLL_UP, s2)).toBe(700); - expect(getScrollOffsetForCol(7, ScrollDirection.SCROLL_DOWN, s2)).toBe(202); + expect(getScrollOffsetForCol(7, ScrollDirection.SCROLL_DOWN, s2)).toBe(800); - (facet as TableFacet).frozenGroupInfo = { - [FrozenGroupType.FROZEN_COL]: { + (facet as TableFacet).frozenGroupAreas = { + [FrozenGroupArea.Col]: { width: 100, x: 0, - range: [], + range: [] as number[], }, - [FrozenGroupType.FROZEN_TRAILING_COL]: { + [FrozenGroupArea.TrailingCol]: { width: 100, x: 0, - range: [], + range: [] as number[], }, - [FrozenGroupType.FROZEN_ROW]: { + [FrozenGroupArea.Row]: { height: 0, y: 0, - range: [], + range: [] as number[], }, - [FrozenGroupType.FROZEN_TRAILING_ROW]: { + [FrozenGroupArea.TrailingRow]: { height: 0, y: 0, - range: [], + range: [] as number[], }, }; + (s2.facet as TableFacet).getFrozenOptions = jest.fn().mockReturnValue({ + colCount: 1, + rowCount: 1, + trailingColCount: 1, + trailingRowCount: 1, + }); expect(getScrollOffsetForCol(7, ScrollDirection.SCROLL_UP, s2)).toBe(600); - expect(getScrollOffsetForCol(7, ScrollDirection.SCROLL_DOWN, s2)).toBe(302); + expect(getScrollOffsetForCol(7, ScrollDirection.SCROLL_DOWN, s2)).toBe(900); facet.panelBBox = { + viewportHeight: facet.getCanvasSize().height, height: facet.getCanvasSize().height, } as any; @@ -510,26 +523,26 @@ describe('Interaction Data Cell Brush Selection Tests', () => { expect(getScrollOffsetForRow(7, ScrollDirection.SCROLL_UP, s2)).toBe(700); expect(getScrollOffsetForRow(7, ScrollDirection.SCROLL_DOWN, s2)).toBe(320); - (facet as TableFacet).frozenGroupInfo = { - [FrozenGroupType.FROZEN_COL]: { - x: 0, + (facet as TableFacet).frozenGroupAreas = { + [FrozenGroupArea.Col]: { width: 0, - range: [], - }, - [FrozenGroupType.FROZEN_TRAILING_COL]: { x: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingCol]: { width: 0, - range: [], + x: 0, + range: [] as number[], }, - [FrozenGroupType.FROZEN_ROW]: { - y: 0, - range: [], + [FrozenGroupArea.Row]: { height: 100, + y: 0, + range: [] as number[], }, - [FrozenGroupType.FROZEN_TRAILING_ROW]: { + [FrozenGroupArea.TrailingRow]: { height: 100, y: 0, - range: [], + range: [] as number[], }, }; expect(getScrollOffsetForRow(7, ScrollDirection.SCROLL_UP, s2)).toBe(600); @@ -549,26 +562,26 @@ describe('Interaction Data Cell Brush Selection Tests', () => { expect(validateYIndex(10)).toBe(null); expect(validateYIndex(9)).toBe(9); - (s2.facet as TableFacet).frozenGroupInfo = { - [FrozenGroupType.FROZEN_COL]: { + (s2.facet as TableFacet).frozenGroupAreas = { + [FrozenGroupArea.Col]: { width: 0, x: 0, - range: [0, 1], + range: [0, 1] as number[], }, - [FrozenGroupType.FROZEN_TRAILING_COL]: { + [FrozenGroupArea.TrailingCol]: { width: 0, x: 0, - range: [8, 9], + range: [8, 9] as number[], }, - [FrozenGroupType.FROZEN_ROW]: { - y: 0, + [FrozenGroupArea.Row]: { height: 0, - range: [0, 1], - }, - [FrozenGroupType.FROZEN_TRAILING_ROW]: { y: 0, + range: [0, 1] as number[], + }, + [FrozenGroupArea.TrailingRow]: { height: 0, - range: [8, 9], + y: 0, + range: [8, 9] as number[], }, }; diff --git a/packages/s2-core/__tests__/unit/interaction/selected-cell-move-spec.ts b/packages/s2-core/__tests__/unit/interaction/selected-cell-move-spec.ts index 0d9a69a79e..6903469bca 100644 --- a/packages/s2-core/__tests__/unit/interaction/selected-cell-move-spec.ts +++ b/packages/s2-core/__tests__/unit/interaction/selected-cell-move-spec.ts @@ -2,7 +2,11 @@ import { createFakeSpreadSheet, createMockCellInfo } from 'tests/util/helpers'; import type { BaseFacet } from '../../../src/facet'; import type { InternalFullyTheme, OffsetConfig } from '@/common/interface'; import type { SpreadSheet } from '@/sheet-type'; -import { InteractionKeyboardKey, S2Event } from '@/common/constant'; +import { + FrozenGroupArea, + InteractionKeyboardKey, + S2Event, +} from '@/common/constant'; import { SelectedCellMove } from '@/interaction/selected-cell-move'; jest.mock('@/interaction/event-controller'); @@ -58,6 +62,7 @@ describe('Interaction Keyboard Move Tests', () => { viewportHeight: 200, viewportWidth: 200, }, + panelScrollGroupIndexes: [0, 1, 0, 1], viewCellWidths: [], viewCellHeights: { getCellOffsetY: () => 0, @@ -67,6 +72,35 @@ describe('Interaction Keyboard Move Tests', () => { cornerBBox: { width: 80, }, + frozenGroupAreas: { + [FrozenGroupArea.Col]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingCol]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.Row]: { + height: 0, + y: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingRow]: { + height: 0, + y: 0, + range: [] as number[], + }, + }, + getFrozenOptions: jest.fn().mockReturnValue({ + colCount: 0, + trailingColCount: 0, + rowCount: 0, + trailingRowCount: 0, + }), + getCellRange: jest.fn().mockReturnValue({ start: 0, end: 1 }), } as unknown as BaseFacet; s2.interaction.intercepts.clear(); s2.interaction.isEqualStateName = () => false; @@ -356,7 +390,7 @@ describe('Interaction Keyboard Move Tests', () => { expect(s2.facet.getScrollOffset()).toEqual({ scrollX: 0, - scrollY: 1, + scrollY: 0, }); }); }); diff --git a/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts b/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts index e0722b6eaa..bd4b7c11fd 100644 --- a/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts +++ b/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts @@ -678,10 +678,9 @@ describe('PivotSheet Tests', () => { // sheet group expect(s2.facet.backgroundGroup.children).toHaveLength(1); - expect(s2.facet.foregroundGroup.children).toHaveLength(9); - + expect(s2.facet.foregroundGroup.children).toHaveLength(10); // panel scroll group - expect(s2.facet.panelGroup.children).toHaveLength(7); + expect(s2.facet.panelGroup.children).toHaveLength(9); expect( s2.facet.panelGroup.getElementsByName(KEY_GROUP_PANEL_SCROLL), ).toHaveLength(1); diff --git a/packages/s2-core/__tests__/unit/utils/condition/condition-spec.ts b/packages/s2-core/__tests__/unit/utils/condition/condition-spec.ts index 04080c343b..1adc53c196 100644 --- a/packages/s2-core/__tests__/unit/utils/condition/condition-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/condition/condition-spec.ts @@ -1,4 +1,4 @@ -import type { Condition, RawData } from '../../../../src/common/interface'; +import type { RawData, TextCondition } from '../../../../src/common/interface'; import { findFieldCondition, getIconPosition, @@ -32,7 +32,7 @@ describe('getIconLayoutPosition Test', () => { describe('getFieldCondition Test', () => { test('should find the condition where fill is green', () => { - const conditions: Condition[] = [ + const conditions: TextCondition[] = [ { field: 'value', mapping: () => ({ fill: 'red' }), @@ -47,7 +47,7 @@ describe('getFieldCondition Test', () => { }); test('should not find the condition where fill is orange', () => { - const conditions: Condition[] = [ + const conditions: TextCondition[] = [ { field: 'value', mapping: () => ({ fill: 'red' }), diff --git a/packages/s2-core/__tests__/unit/utils/data-set-operate-spec.ts b/packages/s2-core/__tests__/unit/utils/data-set-operate-spec.ts index 94532b02e5..41d1aa57c4 100644 --- a/packages/s2-core/__tests__/unit/utils/data-set-operate-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/data-set-operate-spec.ts @@ -77,7 +77,7 @@ describe('Data Set Operate Test', () => { it('should get correct list by empty sorted list', () => { expect(getListBySorted(list, [])).toEqual(['浙江省', '四川省']); expect(getListBySorted(list, ['', ''])).toEqual(['浙江省', '四川省']); - expect(getListBySorted(list, [null, undefined])).toEqual([ + expect(getListBySorted(list, [null as any, undefined as any])).toEqual([ '浙江省', '四川省', ]); diff --git a/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts b/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts index 0849fea05b..0b50d8f6b0 100644 --- a/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/copy-spec.ts @@ -846,11 +846,11 @@ describe('Pivot Table Core Data Process', () => { }); const meta: Meta[] = [ - { field: 'number', name: '数量', formatter: (v: string) => `${v}元` }, - { field: 'province', formatter: (v: string) => `${v}-省` }, - { field: 'city', name: '城市', formatter: (v: string) => `${v}-市` }, - { field: 'type', formatter: (v: string) => `${v}-类` }, - { field: 'sub_type', formatter: (v: string) => `${v}-子类` }, + { field: 'number', name: '数量', formatter: (v: unknown) => `${v}元` }, + { field: 'province', formatter: (v: unknown) => `${v}-省` }, + { field: 'city', name: '城市', formatter: (v: unknown) => `${v}-市` }, + { field: 'type', formatter: (v: unknown) => `${v}-类` }, + { field: 'sub_type', formatter: (v: unknown) => `${v}-子类` }, ]; s2.setDataCfg(getDataCfg(meta)); diff --git a/packages/s2-core/__tests__/unit/utils/export/export-table-spec.ts b/packages/s2-core/__tests__/unit/utils/export/export-table-spec.ts index b586fcd2f7..590ef81507 100644 --- a/packages/s2-core/__tests__/unit/utils/export/export-table-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/export/export-table-spec.ts @@ -13,9 +13,9 @@ import { } from '../../../util/helpers'; import { CSV_SEPARATOR, - FormatOptions, - S2DataConfig, - S2Options, + type FormatOptions, + type S2DataConfig, + type S2Options, LINE_SEPARATOR, TAB_SEPARATOR, } from '../../../../src'; diff --git a/packages/s2-core/__tests__/unit/utils/frozen-util-spec.ts b/packages/s2-core/__tests__/unit/utils/frozen-util-spec.ts index e31c18d9ac..b98c8ba491 100644 --- a/packages/s2-core/__tests__/unit/utils/frozen-util-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/frozen-util-spec.ts @@ -1,12 +1,12 @@ -import type { S2TableSheetFrozenOptions } from '@/common/interface/s2Options'; +import { FrozenGroupType } from '../../../src'; +import type { S2BaseFrozenOptions } from '@/common/interface/s2Options'; import { getValidFrozenOptions } from '@/utils/layout/frozen'; import { - getFrozenDataCellType, + getFrozenGroupTypeByCell, calculateFrozenCornerCells, splitInViewIndexesWithFrozen, getCellRange, } from '@/facet/utils'; -import { FrozenCellType } from '@/common/constant/frozen'; import type { Indexes } from '@/utils/indexes'; import type { ViewCellHeights } from '@/facet/layout/interface'; @@ -37,21 +37,21 @@ describe('Frozen util test', () => { }); }); - describe('getFrozenDataCellType', () => { + describe('getFrozenGroupTypeByCell', () => { it('should return correct data cell type', () => { const colLength = 10; const cellRange = { start: 0, end: 499, }; - const frozenOptions: S2TableSheetFrozenOptions = { + const frozenOptions: S2BaseFrozenOptions = { colCount: 2, rowCount: 2, trailingColCount: 2, trailingRowCount: 2, }; - const colType = getFrozenDataCellType( + const colType = getFrozenGroupTypeByCell( { rowIndex: 100, colIndex: 1, @@ -61,9 +61,9 @@ describe('Frozen util test', () => { cellRange, ); - expect(colType).toBe(FrozenCellType.COL); + expect(colType).toBe(FrozenGroupType.Col); - const rowType = getFrozenDataCellType( + const rowType = getFrozenGroupTypeByCell( { rowIndex: 1, colIndex: 5, @@ -73,9 +73,9 @@ describe('Frozen util test', () => { cellRange, ); - expect(rowType).toBe(FrozenCellType.ROW); + expect(rowType).toBe(FrozenGroupType.Row); - const trailingColType = getFrozenDataCellType( + const trailingColType = getFrozenGroupTypeByCell( { rowIndex: 100, colIndex: 9, @@ -85,9 +85,9 @@ describe('Frozen util test', () => { cellRange, ); - expect(trailingColType).toBe(FrozenCellType.TRAILING_COL); + expect(trailingColType).toBe(FrozenGroupType.TrailingCol); - const trailingRowType = getFrozenDataCellType( + const trailingRowType = getFrozenGroupTypeByCell( { rowIndex: 499, colIndex: 5, @@ -97,9 +97,9 @@ describe('Frozen util test', () => { cellRange, ); - expect(trailingRowType).toBe(FrozenCellType.TRAILING_ROW); + expect(trailingRowType).toBe(FrozenGroupType.TrailingRow); - const scrollTypeNearTopLeft = getFrozenDataCellType( + const scrollTypeNearTopLeft = getFrozenGroupTypeByCell( { rowIndex: 2, colIndex: 2, @@ -109,9 +109,9 @@ describe('Frozen util test', () => { cellRange, ); - expect(scrollTypeNearTopLeft).toBe(FrozenCellType.SCROLL); + expect(scrollTypeNearTopLeft).toBe(FrozenGroupType.Scroll); - const scrollTypeNearTopRight = getFrozenDataCellType( + const scrollTypeNearTopRight = getFrozenGroupTypeByCell( { rowIndex: 2, colIndex: 7, @@ -121,9 +121,9 @@ describe('Frozen util test', () => { cellRange, ); - expect(scrollTypeNearTopRight).toBe(FrozenCellType.SCROLL); + expect(scrollTypeNearTopRight).toBe(FrozenGroupType.Scroll); - const scrollTypeNearBottomRight = getFrozenDataCellType( + const scrollTypeNearBottomRight = getFrozenGroupTypeByCell( { rowIndex: 497, colIndex: 7, @@ -133,9 +133,9 @@ describe('Frozen util test', () => { cellRange, ); - expect(scrollTypeNearBottomRight).toBe(FrozenCellType.SCROLL); + expect(scrollTypeNearBottomRight).toBe(FrozenGroupType.Scroll); - const scrollTypeNearBottomLeft = getFrozenDataCellType( + const scrollTypeNearBottomLeft = getFrozenGroupTypeByCell( { rowIndex: 497, colIndex: 2, @@ -145,13 +145,13 @@ describe('Frozen util test', () => { cellRange, ); - expect(scrollTypeNearBottomLeft).toBe(FrozenCellType.SCROLL); + expect(scrollTypeNearBottomLeft).toBe(FrozenGroupType.Scroll); }); }); describe('calculateFrozenCornerCells', () => { it('should return correct frozen corner cell', () => { - const frozenOptions: S2TableSheetFrozenOptions = { + const frozenOptions: S2BaseFrozenOptions = { colCount: 1, rowCount: 2, trailingColCount: 1, @@ -166,17 +166,15 @@ describe('Frozen util test', () => { expect( calculateFrozenCornerCells(frozenOptions, colLength, cellRange), ).toStrictEqual({ - bottom: [ - { - x: 0, - y: 999, - }, + frozenBottomLeftGroup: [{ x: 0, y: 999 }], + frozenBottomRightGroup: [ { x: 3, y: 999, }, ], - top: [ + + frozenTopLeftGroup: [ { x: 0, y: 0, @@ -185,6 +183,8 @@ describe('Frozen util test', () => { x: 0, y: 1, }, + ], + frozenTopRightGroup: [ { x: 3, y: 0, @@ -206,7 +206,7 @@ describe('Frozen util test', () => { start: 0, end: 999, }; - const frozenOptions: S2TableSheetFrozenOptions = { + const frozenOptions: S2BaseFrozenOptions = { colCount: 1, rowCount: 2, trailingColCount: 1, diff --git a/packages/s2-core/__tests__/unit/utils/indexes-spec.ts b/packages/s2-core/__tests__/unit/utils/indexes-spec.ts index 8015e32a20..e47a140166 100644 --- a/packages/s2-core/__tests__/unit/utils/indexes-spec.ts +++ b/packages/s2-core/__tests__/unit/utils/indexes-spec.ts @@ -3,6 +3,7 @@ import { diffIndexes, diffPanelIndexes, isXYInRange, + type Indexes, } from '../../../src/utils/indexes'; describe('indexes test', () => { @@ -16,7 +17,7 @@ describe('indexes test', () => { }); test('#allIndexes()', () => { - expect(allIndexes([])).toEqual([]); + expect(allIndexes([] as unknown as Indexes)).toEqual([]); expect(allIndexes([0, 1, 0, 1])).toEqual([ [0, 0], [0, 1], @@ -26,7 +27,9 @@ describe('indexes test', () => { }); test('#diffIndexes()', () => { - expect(diffIndexes([], [])).toEqual({ + expect( + diffIndexes([] as unknown as Indexes, [] as unknown as Indexes), + ).toEqual({ add: [], remove: [], }); @@ -60,7 +63,12 @@ describe('indexes test', () => { }); test('#diffPanelIndexes()', () => { - expect(diffPanelIndexes({ center: [] }, { center: [] })).toEqual({ + expect( + diffPanelIndexes( + { center: [] as unknown as Indexes }, + { center: [] as unknown as Indexes }, + ), + ).toEqual({ add: [], remove: [], }); diff --git a/packages/s2-core/__tests__/util/helpers.ts b/packages/s2-core/__tests__/util/helpers.ts index e8be5b650b..b49abfb876 100644 --- a/packages/s2-core/__tests__/util/helpers.ts +++ b/packages/s2-core/__tests__/util/helpers.ts @@ -19,10 +19,10 @@ import { type BaseDataSet, type Node, Hierarchy, - EventController, - FormatOptions, + type FormatOptions, asyncGetAllPlainData, TAB_SEPARATOR, + EventController, getDefaultSeriesNumberText, } from '../../src'; @@ -40,7 +40,12 @@ import type { import { PivotSheet, SpreadSheet, TableSheet } from '@/sheet-type'; import type { BaseTooltip } from '@/ui/tooltip'; import { customMerge } from '@/utils/merge'; -import { DEFAULT_OPTIONS, FrozenGroupType } from '@/common/constant'; +import { + DEFAULT_FROZEN_COUNTS, + DEFAULT_OPTIONS, + FrozenGroupArea, + FrozenGroupType, +} from '@/common/constant'; import type { BaseFacet } from '@/facet'; import type { PanelBBox } from '@/facet/bbox/panel-bbox'; @@ -149,18 +154,49 @@ export const createFakeSpreadSheet = (config?: { getColLeafNodes: () => [], getInitColLeafNodes: () => [], getHeaderCells: () => [], + getPaginationScrollY: jest.fn().mockReturnValue(0), getHiddenColumnsInfo: jest.fn(), getCellAdaptiveHeight: jest.fn(), getRowLeafNodeByIndex: jest.fn(), getColLeafNodeByIndex: jest.fn(), - frozenGroupInfo: { - [FrozenGroupType.FROZEN_ROW]: {}, - [FrozenGroupType.FROZEN_COL]: {}, - [FrozenGroupType.FROZEN_TRAILING_ROW]: {}, - [FrozenGroupType.FROZEN_TRAILING_COL]: {}, + frozenGroupAreas: { + [FrozenGroupArea.Col]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingCol]: { + width: 0, + x: 0, + range: [] as number[], + }, + [FrozenGroupArea.Row]: { + height: 0, + y: 0, + range: [] as number[], + }, + [FrozenGroupArea.TrailingRow]: { + height: 0, + y: 0, + range: [] as number[], + }, + }, + frozenGroups: { + [FrozenGroupType.Row]: {}, + [FrozenGroupType.TrailingRow]: {}, + [FrozenGroupType.Col]: {}, + [FrozenGroupType.TrailingCol]: {}, + [FrozenGroupType.TopLeft]: {}, + [FrozenGroupType.TopRight]: {}, + [FrozenGroupType.BottomLeft]: {}, + [FrozenGroupType.BottomRight]: {}, }, + getCellRange: jest.fn().mockReturnValue({ start: 0, end: 100 }), cornerBBox: {}, destroy: jest.fn(), + getFrozenOptions: jest.fn().mockReturnValue({ + ...DEFAULT_FROZEN_COUNTS, + }), } as unknown as BaseFacet; s2.container.render = jest.fn(); s2.store = new Store(); @@ -184,7 +220,7 @@ export const createFakeSpreadSheet = (config?: { s2.getCell = jest.fn(); s2.isHierarchyTreeType = jest.fn(); s2.getCanvasElement = () => - s2.container.getContextService().getDomElement() as HTMLCanvasElement; + s2.container.getContextService().getDomElement() as any; s2.getCanvasConfig = () => s2.container.getConfig(); s2.isCustomHeaderFields = jest.fn(() => false); s2.isCustomRowFields = jest.fn(() => false); @@ -195,7 +231,6 @@ export const createFakeSpreadSheet = (config?: { s2.isCustomRowFields = jest.fn(); s2.getTotalsConfig = jest.fn(); s2.getLayoutWidthType = jest.fn(); - s2.enableFrozenHeaders = jest.fn(); s2.measureTextWidth = jest.fn(); s2.isFrozenRowHeader = jest.fn(); s2.getSeriesNumberText = jest.fn(() => getDefaultSeriesNumberText()); @@ -207,6 +242,7 @@ export const createFakeSpreadSheet = (config?: { const interaction = new RootInteraction(s2 as unknown as SpreadSheet); s2.interaction = interaction; + s2.interaction.intercepts = new Set(); s2.interaction.eventController = new EventController(s2); return s2; diff --git a/packages/s2-core/src/cell/col-cell.ts b/packages/s2-core/src/cell/col-cell.ts index 1de3b3a9ec..69600f125a 100644 --- a/packages/s2-core/src/cell/col-cell.ts +++ b/packages/s2-core/src/cell/col-cell.ts @@ -4,6 +4,7 @@ import { adjustTextIconPositionWhileScrolling } from '../utils/cell/text-scrolli import { normalizeTextAlign } from '../utils/normalize'; import { CellType, + FrozenGroupArea, HORIZONTAL_RESIZE_AREA_KEY_PRE, KEY_GROUP_COL_RESIZE_AREA, ResizeAreaEffect, @@ -32,6 +33,7 @@ import { getResizeAreaAttrs, shouldAddResizeArea, } from '../utils/interaction/resize'; +import type { FrozenFacet } from '../facet'; import { isEqualDisplaySiblingNodeId } from './../utils/hide-columns'; import { HeaderCell } from './header-cell'; @@ -105,30 +107,47 @@ export class ColCell extends HeaderCell { * @returns viewport */ protected handleViewport(): AreaRange { - /** - * p(x, y) - * +----------------------+ x - * | +---------------> - * | viewport | |ColCell | - * | |-|---------+ - * +--------------------|-+ - * | - * y | - * v - * - * 将 viewport 坐标(p)映射到 col header 的坐标体系中,简化计算逻辑 - * - */ - const { width, cornerWidth = 0, scrollX = 0 } = this.getHeaderConfig(); + if (this.meta.isFrozen) { + return { + start: 0, + size: Number.POSITIVE_INFINITY, + }; + } - const scrollContainsRowHeader = !this.spreadsheet.isFrozenRowHeader(); + const { + viewportWidth, + cornerWidth = 0, + scrollX = 0, + position, + } = this.getHeaderConfig(); - const viewport: AreaRange = { - start: scrollX - (scrollContainsRowHeader ? cornerWidth : 0), - size: width + (scrollContainsRowHeader ? cornerWidth : 0), - }; + const frozenGroupAreas = (this.spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; - return viewport; + const frozenColGroupWidth = frozenGroupAreas[FrozenGroupArea.Col].width; + const frozenTrailingColGroupWidth = + frozenGroupAreas[FrozenGroupArea.TrailingCol].width; + + if (this.spreadsheet.isFrozenRowHeader()) { + return { + start: scrollX + frozenColGroupWidth, + size: viewportWidth - frozenColGroupWidth - frozenTrailingColGroupWidth, + }; + } + + const scrollXUntilColStickToLeft = frozenColGroupWidth + ? cornerWidth + : position.x; + + return { + start: + frozenColGroupWidth + Math.max(0, scrollX - scrollXUntilColStickToLeft), + size: + viewportWidth - + frozenColGroupWidth - + frozenTrailingColGroupWidth + + Math.min(scrollX, scrollXUntilColStickToLeft), + }; } protected getTextPosition(): PointLike { diff --git a/packages/s2-core/src/cell/data-cell.ts b/packages/s2-core/src/cell/data-cell.ts index 9439556967..9681be99b8 100644 --- a/packages/s2-core/src/cell/data-cell.ts +++ b/packages/s2-core/src/cell/data-cell.ts @@ -480,10 +480,13 @@ export class DataCell extends BaseCell { } } - /** - * 预留给明细表使用,透视表数据格不需要绘制 border, 已经交由 grid 处理 - */ - public override drawBorders(): void {} + public override drawBorders(): void { + if (!this.meta.isFrozenCorner) { + return; + } + + BaseCell.prototype.drawBorders.call(this); + } /** * Find current field related condition diff --git a/packages/s2-core/src/cell/row-cell.ts b/packages/s2-core/src/cell/row-cell.ts index 883229a375..fcaae5e57f 100644 --- a/packages/s2-core/src/cell/row-cell.ts +++ b/packages/s2-core/src/cell/row-cell.ts @@ -2,7 +2,7 @@ import type { PointLike } from '@antv/g'; import { find, get, merge } from 'lodash'; import { CellType, - FrozenGroupType, + FrozenGroupArea, KEY_GROUP_ROW_RESIZE_AREA, ResizeAreaEffect, ResizeDirectionType, @@ -244,7 +244,6 @@ export class RowCell extends HeaderCell { viewportHeight: headerHeight, scrollX = 0, scrollY = 0, - spreadsheet, } = this.getHeaderConfig(); const resizeAreaBBox: SimpleBBox = { @@ -254,30 +253,34 @@ export class RowCell extends HeaderCell { height: resizeStyle.size!, }; - const isFrozen = this.getMeta().isFrozen; + const isFrozen = this.meta.isFrozen; - const frozenRowGroupHeight = (spreadsheet.facet as FrozenFacet) - .frozenGroupInfo[FrozenGroupType.FROZEN_ROW]?.height; + const frozenGroupAreas = (this.spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; + const frozenRowGroup = frozenGroupAreas[FrozenGroupArea.Row]; + + const frozenTrailingRowGroup = + frozenGroupAreas[FrozenGroupArea.TrailingRow]; const resizeClipAreaBBox: SimpleBBox = { x: 0, - y: frozenRowGroupHeight, + y: isFrozen ? 0 : frozenRowGroup.height, width: headerWidth, - height: headerHeight, + height: isFrozen + ? Number.POSITIVE_INFINITY + : headerHeight - frozenRowGroup.height - frozenTrailingRowGroup.height, }; if ( - !isFrozen && !shouldAddResizeArea(resizeAreaBBox, resizeClipAreaBBox, { scrollX, - scrollY, + scrollY: isFrozen ? 0 : scrollY, }) ) { return; } - const offsetX = position.x + x - scrollX; - const offsetY = position.y + y - (isFrozen ? 0 : scrollY); + const { offsetX, offsetY } = this.getHorizontalResizeAreaOffset(); const resizeAreaWidth = this.spreadsheet.isFrozenRowHeader() ? headerWidth - position.x - (x - scrollX) @@ -309,6 +312,45 @@ export class RowCell extends HeaderCell { ); } + protected getHorizontalResizeAreaOffset() { + const { + position, + viewportHeight: headerHeight, + scrollX = 0, + scrollY = 0, + } = this.getHeaderConfig(); + + const { x, y } = this.getBBoxByType(); + + const frozenGroupAreas = (this.spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; + const frozenRowGroup = frozenGroupAreas[FrozenGroupArea.Row]; + + const frozenTrailingRowGroup = + frozenGroupAreas[FrozenGroupArea.TrailingRow]; + + const offsetX = position.x + x - scrollX; + + let offsetY: number = position.y; + + if (this.meta.isFrozenHead) { + offsetY += y - frozenRowGroup.y; + } else if (this.meta.isFrozenTrailing) { + offsetY += + headerHeight - + frozenTrailingRowGroup.height + + y - + frozenTrailingRowGroup.y; + } else { + offsetY += y - scrollY; + } + + return { + offsetX, + offsetY, + }; + } + protected getContentIndent() { if (!this.spreadsheet.isHierarchyTreeType()) { return 0; @@ -374,14 +416,26 @@ export class RowCell extends HeaderCell { } protected handleViewport() { + if (this.meta.isFrozen) { + return { + start: 0, + size: Number.POSITIVE_INFINITY, + }; + } + const { scrollY, viewportHeight } = this.getHeaderConfig(); - const frozenRowGroupHeight = (this.spreadsheet.facet as FrozenFacet) - ?.frozenGroupInfo[FrozenGroupType.FROZEN_ROW].height; + const frozenGroupAreas = (this.spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; + + const frozenRowGroupHeight = frozenGroupAreas[FrozenGroupArea.Row].height; + const frozenTrailingRowGroupHeight = + frozenGroupAreas[FrozenGroupArea.TrailingRow].height; const viewport: AreaRange = { - start: this.getMeta().isFrozen ? 0 : scrollY! + frozenRowGroupHeight, - size: viewportHeight - frozenRowGroupHeight, + start: scrollY! + frozenRowGroupHeight, + size: + viewportHeight - frozenRowGroupHeight - frozenTrailingRowGroupHeight, }; return viewport; diff --git a/packages/s2-core/src/cell/series-number-cell.ts b/packages/s2-core/src/cell/series-number-cell.ts index a1b7b1aeaa..23370597ff 100644 --- a/packages/s2-core/src/cell/series-number-cell.ts +++ b/packages/s2-core/src/cell/series-number-cell.ts @@ -1,13 +1,10 @@ import type { PointLike } from '@antv/g'; import { CellType } from '../common/constant/interaction'; -import type { AreaRange, FormatResult } from '../common/interface'; +import type { FormatResult } from '../common/interface'; import { CellBorderPosition, CellClipBox } from '../common/interface/basic'; -import { getHorizontalTextIconPosition } from '../utils/cell/cell'; -import { adjustTextIconPositionWhileScrolling } from '../utils/cell/text-scrolling'; -import { normalizeTextAlign } from '../utils/normalize'; -import { HeaderCell } from './header-cell'; +import { RowCell } from './row-cell'; -export class SeriesNumberCell extends HeaderCell { +export class SeriesNumberCell extends RowCell { public get cellType() { return CellType.ROW_CELL; } @@ -65,46 +62,6 @@ export class SeriesNumberCell extends HeaderCell { return width; } - protected getTextPosition(): PointLike { - const { scrollY, viewportHeight } = this.headerConfig; - const textStyle = this.getTextStyle(); - const { cell } = this.getStyle()!; - const viewport: AreaRange = { - start: scrollY!, - size: viewportHeight, - }; - - const textArea = this.getBBoxByType(CellClipBox.CONTENT_BOX); - - const { textStart } = adjustTextIconPositionWhileScrolling( - viewport, - { - start: textArea.y, - size: textArea.height, - }, - { - align: normalizeTextAlign(textStyle.textBaseline!), - size: { - textSize: textStyle.fontSize!, - }, - padding: { - start: cell?.padding?.top!, - end: cell?.padding?.bottom!, - }, - }, - ); - - const { textX } = getHorizontalTextIconPosition({ - bbox: textArea, - textAlign: textStyle.textAlign!, - textWidth: this.getActualTextWidth(), - iconStyle: this.getIconStyle()!, - groupedIcons: this.groupedIcons, - }); - - return { x: textX, y: textStart }; - } - protected isBolderText() { return false; } diff --git a/packages/s2-core/src/cell/table-col-cell.ts b/packages/s2-core/src/cell/table-col-cell.ts index 5dbf055ce4..efd04a5d4f 100644 --- a/packages/s2-core/src/cell/table-col-cell.ts +++ b/packages/s2-core/src/cell/table-col-cell.ts @@ -1,20 +1,15 @@ import { find } from 'lodash'; import { ColCell } from '../cell/col-cell'; import { - FrozenGroupType, + FrozenGroupArea, HORIZONTAL_RESIZE_AREA_KEY_PRE, - KEY_GROUP_FROZEN_COL_RESIZE_AREA, } from '../common/constant'; import type { FormatResult } from '../common/interface'; -import type { AreaRange } from '../common/interface/scroll'; import type { SimpleBBox } from '../engine'; import type { BaseHeaderConfig } from '../facet/header'; import { formattedFieldValue } from '../utils/cell/header-cell'; import { renderRect } from '../utils/g-renders'; -import { - getOrCreateResizeAreaGroupById, - shouldAddResizeArea, -} from '../utils/interaction/resize'; +import { shouldAddResizeArea } from '../utils/interaction/resize'; import { getSortTypeIcon } from '../utils/sort-action'; import type { FrozenFacet } from '../facet/frozen-facet'; @@ -62,10 +57,11 @@ export class TableColCell extends ColCell { height, }; - const frozenGroupInfo = (spreadsheet.facet as FrozenFacet).frozenGroupInfo; - const colWidth = frozenGroupInfo[FrozenGroupType.FROZEN_COL].width; + const frozenGroupAreas = (spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; + const colWidth = frozenGroupAreas[FrozenGroupArea.Col].width; const trailingColWidth = - frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_COL].width; + frozenGroupAreas[FrozenGroupArea.TrailingCol].width; const resizeClipAreaBBox: SimpleBBox = { x: colWidth, @@ -82,32 +78,39 @@ export class TableColCell extends ColCell { protected getVerticalResizeAreaOffset() { const { x, y } = this.meta; - const { scrollX = 0, position } = this.getHeaderConfig(); + const { + scrollX = 0, + position, + spreadsheet, + viewportWidth, + } = this.getHeaderConfig(); - if (this.getMeta().isFrozen) { - return { - x: position?.x + x, - y: position?.y + y, - }; + const frozenGroupAreas = (spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; + + const frozenColGroup = frozenGroupAreas[FrozenGroupArea.Col]; + const frozenTrailingColGroup = + frozenGroupAreas[FrozenGroupArea.TrailingCol]; + let offsetX = position?.x; + + if (this.getMeta().isFrozenHead) { + offsetX += x - frozenColGroup.x; + } else if (this.getMeta().isFrozenTrailing) { + offsetX += + x - + frozenTrailingColGroup.x + + viewportWidth - + frozenTrailingColGroup.width; + } else { + offsetX += x - scrollX; } return { - x: position?.x + x - scrollX, + x: offsetX, y: position?.y + y, }; } - protected getColResizeArea() { - if (!this.getMeta().isFrozen) { - return super.getColResizeArea(); - } - - return getOrCreateResizeAreaGroupById( - this.spreadsheet, - KEY_GROUP_FROZEN_COL_RESIZE_AREA, - ); - } - protected isSortCell() { return true; } @@ -140,14 +143,4 @@ export class TableColCell extends ColCell { fillOpacity: backgroundColorOpacity, }); } - - protected handleViewport(): AreaRange { - const viewport = super.handleViewport(); - - if (this.getMeta().isFrozen) { - viewport.start = 0; - } - - return viewport; - } } diff --git a/packages/s2-core/src/cell/table-data-cell.ts b/packages/s2-core/src/cell/table-data-cell.ts index 7f92f68f88..ffe228f02d 100644 --- a/packages/s2-core/src/cell/table-data-cell.ts +++ b/packages/s2-core/src/cell/table-data-cell.ts @@ -1,17 +1,12 @@ -import { Frame } from '../facet/header/frame'; import { DataCell } from '../cell/data-cell'; import { CellType, - FrozenGroupType, - KEY_GROUP_FROZEN_ROW_RESIZE_AREA, + FrozenGroupArea, KEY_GROUP_ROW_RESIZE_AREA, ResizeAreaEffect, ResizeDirectionType, } from '../common/constant'; -import { - isFrozenRow as isFrozenRowUtil, - isFrozenTrailingRow as isFrozenTrailingRowUtil, -} from '../facet/utils'; +import { isFrozenRow, isFrozenTrailingRow } from '../facet/utils'; import { getOrCreateResizeAreaGroupById, getResizeAreaAttrs, @@ -20,21 +15,12 @@ import { import { CustomRect, type SimpleBBox } from '../engine'; import type { FrozenFacet } from '../facet/frozen-facet'; import type { CellMeta } from '../common'; -import { BaseCell } from './base-cell'; export class TableDataCell extends DataCell { protected getLinkFieldStyle() { return this.theme.rowCell!.text!.linkTextFill!; } - public override drawBorders() { - if (!this.meta.isFrozenCorner) { - return; - } - - BaseCell.prototype.drawBorders.call(this); - } - protected shouldDrawResizeArea() { // 每一行直绘制一条贯穿式 resize 热区 const id = `${this.meta.rowIndex}`; @@ -43,14 +29,8 @@ export class TableDataCell extends DataCell { this.spreadsheet, KEY_GROUP_ROW_RESIZE_AREA, ); - const frozenResizeArea = getOrCreateResizeAreaGroupById( - this.spreadsheet, - KEY_GROUP_FROZEN_ROW_RESIZE_AREA, - ); - return ( - !resizeArea?.getElementById(id) && !frozenResizeArea?.getElementById(id) - ); + return !resizeArea?.getElementById(id); } public drawResizeArea() { @@ -58,92 +38,100 @@ export class TableDataCell extends DataCell { return; } - const { y, height } = this.getBBoxByType(); - const { rowIndex } = this.meta; - const resizeStyle = this.getResizeAreaStyle(); - const { - rowCount: frozenRowCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - } = this.spreadsheet.options.frozen!; - const cellRange = this.spreadsheet.facet.getCellRange(); - const isFrozenRow = isFrozenRowUtil( - rowIndex, - cellRange.start, - frozenRowCount, - ); - const isFrozenTrailingRow = isFrozenTrailingRowUtil( - rowIndex, - cellRange.end, - frozenTrailingRowCount, - ); - const isFrozen = isFrozenRow || isFrozenTrailingRow; - - const resizeAreaId = isFrozen - ? KEY_GROUP_FROZEN_ROW_RESIZE_AREA - : KEY_GROUP_ROW_RESIZE_AREA; - const resizeArea = getOrCreateResizeAreaGroupById( this.spreadsheet, - resizeAreaId, + KEY_GROUP_ROW_RESIZE_AREA, ); if (!resizeArea) { return; } - const { height: headerHeight, viewportWidth: headerWidth } = - this.spreadsheet.facet.columnHeader.getHeaderConfig(); + const { rowIndex } = this.getMeta(); + const cellRange = this.spreadsheet.facet.getCellRange(); + const { rowCount, trailingRowCount } = ( + this.spreadsheet.facet as FrozenFacet + ).getFrozenOptions(); + + const isFrozenHead = isFrozenRow(rowIndex, cellRange.start, rowCount); + + const isFrozenTrailing = isFrozenTrailingRow( + rowIndex, + cellRange.end, + trailingRowCount, + ); + + const isFrozen = isFrozenHead || isFrozenTrailing; + + const { y, height } = this.getBBoxByType(); + + const { + x: panelBBoxX, + y: panelBBoxY, + viewportWidth, + viewportHeight, + } = this.spreadsheet.facet.panelBBox; const { scrollY } = this.spreadsheet.facet.getScrollOffset(); const paginationSy = this.spreadsheet.facet.getPaginationScrollY(); - let offsetY = - y + headerHeight + Frame.getHorizontalBorderWidth(this.spreadsheet); + const frozenGroupAreas = (this.spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; + const frozenRowGroup = frozenGroupAreas[FrozenGroupArea.Row]; + const frozenTrailingRowGroup = + frozenGroupAreas[FrozenGroupArea.TrailingRow]; + + const resizeStyle = this.getResizeAreaStyle(); - const frozenGroupInfo = (this.spreadsheet.facet as FrozenFacet) - .frozenGroupInfo; - const rowHeight = frozenGroupInfo[FrozenGroupType.FROZEN_ROW].height; - const rowTrailingHeight = - frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_ROW].height; + const width = panelBBoxX + viewportWidth; + const resizeClipAreaBBox: SimpleBBox = { + x: 0, + y: isFrozen ? 0 : frozenRowGroup.height, + width, + height: isFrozen + ? Number.POSITIVE_INFINITY + : viewportHeight - + frozenRowGroup.height - + frozenTrailingRowGroup.height, + }; const resizeAreaBBox: SimpleBBox = { x: 0, y: y + height - resizeStyle.size!, - width: headerWidth, + width, height: resizeStyle.size!, }; - const resizeClipAreaBBox: SimpleBBox = { - x: 0, - y: rowHeight, - width: headerWidth, - height: - this.spreadsheet.facet.panelBBox.height - rowHeight - rowTrailingHeight, - }; if ( - !isFrozen && !shouldAddResizeArea(resizeAreaBBox, resizeClipAreaBBox, { scrollX: 0, - scrollY, + scrollY: isFrozen ? 0 : paginationSy + scrollY, }) ) { return; } - if (!isFrozen) { - offsetY -= scrollY + paginationSy; + let offsetY = panelBBoxY; + + if (isFrozenHead) { + offsetY += y - frozenRowGroup.y; + } else if (isFrozenTrailing) { + offsetY += + viewportHeight - + frozenTrailingRowGroup.height + + y - + frozenTrailingRowGroup.y; + } else { + offsetY += y - paginationSy - scrollY; } - const resizeWidth = - headerWidth + Frame.getVerticalBorderWidth(this.spreadsheet); - const attrs = getResizeAreaAttrs({ theme: resizeStyle, type: ResizeDirectionType.Vertical, effect: ResizeAreaEffect.Cell, offsetX: 0, offsetY, - width: resizeWidth, + width, height, meta: this.meta, }); @@ -155,7 +143,7 @@ export class TableDataCell extends DataCell { ...attrs.style, x: 0, y: offsetY + height - resizeStyle!.size!, - width: resizeWidth, + width, }, }, attrs.appendInfo, diff --git a/packages/s2-core/src/common/constant/basic.ts b/packages/s2-core/src/common/constant/basic.ts index 2b408860a2..39ba5bbadc 100644 --- a/packages/s2-core/src/common/constant/basic.ts +++ b/packages/s2-core/src/common/constant/basic.ts @@ -23,28 +23,45 @@ export const EMPTY_PLACEHOLDER_GROUP_CONTAINER_Z_INDEX = 4; // group's key export const KEY_GROUP_BACK_GROUND = 'backGroundGroup'; export const KEY_GROUP_FORE_GROUND = 'foreGroundGroup'; + export const KEY_GROUP_PANEL_GROUND = 'panelGroup'; +export const KEY_GROUP_GRID_GROUP = 'gridGroup'; export const KEY_GROUP_PANEL_SCROLL = 'panelScrollGroup'; export const KEY_GROUP_MERGED_CELLS = 'mergedCellsGroup'; -export const KEY_GROUP_PANEL_FROZEN_ROW = 'frozenRowGroup'; -export const KEY_GROUP_PANEL_FROZEN_COL = 'frozenColGroup'; -export const KEY_GROUP_PANEL_FROZEN_TRAILING_ROW = 'frozenTrailingRowGroup'; -export const KEY_GROUP_PANEL_FROZEN_TRAILING_COL = 'frozenTrailingColGroup'; -export const KEY_GROUP_PANEL_FROZEN_TOP = 'frozenTopGroup'; -export const KEY_GROUP_PANEL_FROZEN_BOTTOM = 'frozenBottomGroup'; -export const KEY_GROUP_ROW_RESIZE_AREA = 'rowResizeAreaGroup'; -export const KEY_GROUP_FROZEN_ROW_RESIZE_AREA = 'rowFrozenResizeAreaGroup'; -export const KEY_GROUP_FROZEN_SPLIT_LINE = 'frozenSplitLine'; + +/** + * series number + */ +export const KEY_GROUP_ROW_INDEX_SCROLL = 'rowIndexScrollGroup'; +export const KEY_GROUP_ROW_INDEX_FROZEN = 'rowIndexFrozenGroup'; +export const KEY_GROUP_ROW_INDEX_FROZEN_TRAILING = + 'rowIndexFrozenTrailingGroup'; export const KEY_GROUP_ROW_INDEX_RESIZE_AREA = 'rowIndexResizeAreaGroup'; -export const KEY_GROUP_CORNER_RESIZE_AREA = 'cornerResizeAreaGroup'; -export const KEY_GROUP_COL_RESIZE_AREA = 'colResizeAreaGroup'; -export const KEY_GROUP_FROZEN_COL_RESIZE_AREA = 'colFrozenResizeAreaGroup'; +/** + * row + */ +export const KEY_GROUP_ROW_SCROLL = 'rowScrollGroup'; +export const KEY_GROUP_ROW_HEADER_FROZEN = 'rowHeaderFrozenGroup'; +export const KEY_GROUP_ROW_HEADER_FROZEN_TRAILING = + 'rowHeaderFrozenTrailingGroup'; +export const KEY_GROUP_ROW_RESIZE_AREA = 'rowResizeAreaGroup'; + +/** + * column + */ export const KEY_GROUP_COL_SCROLL = 'colScrollGroup'; export const KEY_GROUP_COL_FROZEN = 'colFrozenGroup'; export const KEY_GROUP_COL_FROZEN_TRAILING = 'colFrozenTrailingGroup'; -export const KEY_GROUP_GRID_GROUP = 'gridGroup'; -export const KEY_GROUP_ROW_SCROLL = 'rowScrollGroup'; -export const KEY_GROUP_ROW_HEADER_FROZEN = 'rowHeaderFrozenGroup'; +export const KEY_GROUP_COL_RESIZE_AREA = 'colResizeAreaGroup'; + +/** + * corner + */ +export const KEY_GROUP_CORNER_SCROLL = 'cornerScrollGroup'; +export const KEY_GROUP_CORNER_RESIZE_AREA = 'cornerResizeAreaGroup'; + +export const KEY_GROUP_FROZEN_SPLIT_LINE = 'frozenSplitLine'; + export const KEY_GROUP_EMPTY_PLACEHOLDER = 'emptyPlaceholderGroup'; export const HORIZONTAL_RESIZE_AREA_KEY_PRE = 'horizontal-resize-area-'; diff --git a/packages/s2-core/src/common/constant/frozen.ts b/packages/s2-core/src/common/constant/frozen.ts index bc92f7982b..5f96c8476e 100644 --- a/packages/s2-core/src/common/constant/frozen.ts +++ b/packages/s2-core/src/common/constant/frozen.ts @@ -1,28 +1,27 @@ -export enum FrozenCellType { - ROW = 'row', - COL = 'col', - TRAILING_ROW = 'trailingRow', - TRAILING_COL = 'trailingCol', - SCROLL = 'scroll', - TOP = 'top', - BOTTOM = 'bottom', +export enum FrozenGroupArea { + Row = 'frozenRow', + Col = 'frozenCol', + TrailingRow = 'frozenTrailingRow', + TrailingCol = 'frozenTrailingCol', } export enum FrozenGroupType { - FROZEN_COL = 'frozenCol', - FROZEN_ROW = 'frozenRow', - FROZEN_TRAILING_COL = 'frozenTrailingCol', - FROZEN_TRAILING_ROW = 'frozenTrailingRow', + Row = 'frozenRowGroup', + Col = 'frozenColGroup', + TrailingCol = 'frozenTrailingColGroup', + TrailingRow = 'frozenTrailingRowGroup', + TopLeft = 'frozenTopLeftGroup', + TopRight = 'frozenTopRightGroup', + BottomLeft = 'frozenBottomLeftGroup', + BottomRight = 'frozenBottomRightGroup', + Scroll = 'scrollGroup', } -export const FrozenCellGroupMap = { - [FrozenCellType.ROW]: 'frozenRowGroup', - [FrozenCellType.COL]: 'frozenColGroup', - [FrozenCellType.TRAILING_COL]: 'frozenTrailingColGroup', - [FrozenCellType.TRAILING_ROW]: 'frozenTrailingRowGroup', - [FrozenCellType.SCROLL]: 'panelScrollGroup', - [FrozenCellType.TOP]: 'frozenTopGroup', - [FrozenCellType.BOTTOM]: 'frozenBottomGroup', +export const FrozenGroupAreaTypeMap = { + [FrozenGroupArea.Row]: FrozenGroupType.Row, + [FrozenGroupArea.Col]: FrozenGroupType.Col, + [FrozenGroupArea.TrailingRow]: FrozenGroupType.TrailingRow, + [FrozenGroupArea.TrailingCol]: FrozenGroupType.TrailingCol, }; export interface FrozenCellIndex { diff --git a/packages/s2-core/src/common/constant/options.ts b/packages/s2-core/src/common/constant/options.ts index 73abc317e2..2ba11244f5 100644 --- a/packages/s2-core/src/common/constant/options.ts +++ b/packages/s2-core/src/common/constant/options.ts @@ -8,7 +8,7 @@ import { ScrollbarPositionType, } from '../constant/interaction'; import type { CellTextWordWrapStyle, S2Style } from '../interface'; -import type { S2Options } from '../interface/s2Options'; +import type { S2Options, S2BaseFrozenOptions } from '../interface/s2Options'; import { DeviceType } from '../interface/s2Options'; import { EMPTY_PLACEHOLDER } from './basic'; @@ -59,6 +59,13 @@ export const DEFAULT_STYLE: S2Style = { }, } as const; +export const DEFAULT_FROZEN_COUNTS: Required = { + rowCount: 0, + colCount: 0, + trailingRowCount: 0, + trailingColCount: 0, +}; + export const DEFAULT_OPTIONS: S2Options = { width: 600, height: 480, @@ -127,10 +134,7 @@ export const DEFAULT_OPTIONS: S2Options = { style: DEFAULT_STYLE, frozen: { rowHeader: true, - rowCount: 0, - colCount: 0, - trailingRowCount: 0, - trailingColCount: 0, + ...DEFAULT_FROZEN_COUNTS, }, hd: true, cornerText: '', diff --git a/packages/s2-core/src/common/interface/frozen.ts b/packages/s2-core/src/common/interface/frozen.ts new file mode 100644 index 0000000000..2c70b70de6 --- /dev/null +++ b/packages/s2-core/src/common/interface/frozen.ts @@ -0,0 +1,14 @@ +import type { FrozenGroup } from '../../group/frozen-group'; +import type { FrozenGroupArea, FrozenGroupType } from '../constant/frozen'; + +export interface AreaBBox { + width?: number; + height?: number; + x?: number; + y?: number; + range: number[]; +} + +export type FrozenGroupAreas = Record; + +export type FrozenGroups = Record; diff --git a/packages/s2-core/src/common/interface/s2Options.ts b/packages/s2-core/src/common/interface/s2Options.ts index af14ca25ed..4b51622e92 100644 --- a/packages/s2-core/src/common/interface/s2Options.ts +++ b/packages/s2-core/src/common/interface/s2Options.ts @@ -291,38 +291,32 @@ export enum DeviceType { MOBILE = 'mobile', } -export interface S2PivotSheetFrozenOptions { +export interface S2PivotSheetFrozenOptions extends S2BaseFrozenOptions { /** * 是否冻结行头 (含角头区域, 透视表有效), * 当值为 number 时,标识行头冻结的最大区域,取值范围: (0, 1),0 表示不固定行头 * 当值为 boolean 时,true 对应冻结最大区域为 0.5, false 对应 0 */ rowHeader?: boolean | number; - - /** - * 是否冻结首行 (适用于总计置于顶部, 树状模式等场景) - */ - firstRow?: boolean; } -export interface S2TableSheetFrozenOptions { +export interface S2BaseFrozenOptions { /** - * 行头冻结数量 (明细表有效) + * 行头冻结数量 */ rowCount?: number; /** - * 列头冻结数量 (明细表有效) + * 行尾冻结数量 */ - colCount?: number; - + trailingRowCount?: number; /** - * 行尾冻结数量 (明细表有效) + * 列头冻结数量 */ - trailingRowCount?: number; + colCount?: number; /** - * 列尾冻结数量 (明细表有效) + * 列尾冻结数量 */ trailingColCount?: number; } @@ -367,7 +361,7 @@ export interface S2Options< /** * 行列冻结 */ - frozen?: S2PivotSheetFrozenOptions & S2TableSheetFrozenOptions; + frozen?: S2PivotSheetFrozenOptions & S2BaseFrozenOptions; } export interface S2RenderOptions { diff --git a/packages/s2-core/src/facet/base-facet.ts b/packages/s2-core/src/facet/base-facet.ts index 07a0d26b1d..5cf9efe3e9 100644 --- a/packages/s2-core/src/facet/base-facet.ts +++ b/packages/s2-core/src/facet/base-facet.ts @@ -82,7 +82,6 @@ import type { CellScrollPosition, ScrollOffset, } from '../common/interface/scroll'; -import type { FrozenGroup } from '../group/frozen-group'; import { PanelScrollGroup } from '../group/panel-scroll-group'; import type { SpreadSheet } from '../sheet-type'; import { ScrollBar, ScrollType } from '../ui/scrollbar'; @@ -133,18 +132,6 @@ export abstract class BaseFacet { public panelScrollGroup: PanelScrollGroup; - public frozenRowGroup: FrozenGroup; - - public frozenColGroup: FrozenGroup; - - public frozenTrailingRowGroup: FrozenGroup; - - public frozenTrailingColGroup: FrozenGroup; - - public frozenTopGroup: FrozenGroup; - - public frozenBottomGroup: FrozenGroup; - // render header/corner/scrollbar/resize public foregroundGroup: Group; @@ -787,6 +774,11 @@ export abstract class BaseFacet { this.viewCellHeights = this.getViewCellHeights(); }; + /** + * 提供给明细表做 rowOffsets 计算的 hook + */ + protected abstract calculateRowOffsets(): void; + getRealScrollX = (scrollX: number, hRowScroll = 0) => this.spreadsheet.isFrozenRowHeader() ? hRowScroll : scrollX; @@ -1557,6 +1549,7 @@ export abstract class BaseFacet { // all cell's width&height this.calculateCellWidthHeight(); + this.calculateRowOffsets(); this.calculateCornerBBox(); this.calculatePanelBBox(); this.bindEvents(); @@ -1983,6 +1976,16 @@ export abstract class BaseFacet { return this.getColNodes().find((node) => node.colIndex === colIndex); } + /** + * 获取在索引范围内的列头叶子节点 + * @example facet.getColLeafNodesByRange(0,10) 获取索引范围在 0(包括 0) 到 10(包括 10)的列头叶子节点 + */ + public getColLeafNodesByRange(minIndex: number, maxIndex: number) { + return this.getColLeafNodes().filter( + (node) => node.colIndex >= minIndex && node.colIndex <= maxIndex, + ); + } + /** * 根据列头索引获取指定列头叶子节点 * @example facet.getColLeafNodes(colIndex) @@ -2073,6 +2076,16 @@ export abstract class BaseFacet { return this.getRowLeafNodes().find((node) => node.rowIndex === rowIndex); } + /** + * 获取在索引范围内的行头叶子节点 + * @example facet.getRowLeafNodesByRange(0,10) 获取索引范围在 0(包括 0) 到 10(包括 10)的行头叶子节点 + */ + public getRowLeafNodesByRange(minIndex: number, maxIndex: number) { + return this.getRowLeafNodes().filter( + (node) => node.rowIndex >= minIndex && node.rowIndex <= maxIndex, + ); + } + /** * 根据 field 获取行头节点 * @example facet.getRowNodeByField('number') @@ -2197,10 +2210,12 @@ export abstract class BaseFacet { * 获取角头单元格 (不含可视区域) */ public getCornerCells(): CornerCell[] { - return filter( - this.getCornerHeader().children, - (element: CornerCell) => element instanceof CornerCell, - ) as unknown[] as CornerCell[]; + const headerChildren = (this.getCornerHeader()?.children || + []) as CornerCell[]; + + return getAllChildCells(headerChildren, CornerCell).filter( + (cell: S2CellType) => cell.cellType === CellType.CORNER_CELL, + ); } /** diff --git a/packages/s2-core/src/facet/bbox/panel-bbox.ts b/packages/s2-core/src/facet/bbox/panel-bbox.ts index 3cea077be9..2a29c56c75 100644 --- a/packages/s2-core/src/facet/bbox/panel-bbox.ts +++ b/packages/s2-core/src/facet/bbox/panel-bbox.ts @@ -38,22 +38,5 @@ export class PanelBBox extends BaseBBox { ); this.maxX = this.x + this.viewportWidth; this.maxY = this.y + this.viewportHeight; - - if (!this.spreadsheet.enableFrozenHeaders()) { - return; - } - - const { trailingColCount = 0, trailingRowCount = 0 } = - this.spreadsheet.options.frozen!; - - if (trailingColCount > 0) { - this.viewportWidth = this.width; - this.maxX = this.x + this.width; - } - - if (trailingRowCount > 0) { - this.viewportHeight = this.height; - this.maxY = this.y + this.height; - } } } diff --git a/packages/s2-core/src/facet/frozen-facet.ts b/packages/s2-core/src/facet/frozen-facet.ts index d71f133a51..ae314ab0b3 100644 --- a/packages/s2-core/src/facet/frozen-facet.ts +++ b/packages/s2-core/src/facet/frozen-facet.ts @@ -3,18 +3,13 @@ import { last } from 'lodash'; import type { DataCell } from '../cell'; import { FRONT_GROUND_GROUP_FROZEN_Z_INDEX, - FrozenCellGroupMap, FrozenGroupType, + FrozenGroupArea, KEY_GROUP_FROZEN_SPLIT_LINE, - KEY_GROUP_PANEL_FROZEN_BOTTOM, - KEY_GROUP_PANEL_FROZEN_COL, - KEY_GROUP_PANEL_FROZEN_ROW, - KEY_GROUP_PANEL_FROZEN_TOP, - KEY_GROUP_PANEL_FROZEN_TRAILING_COL, - KEY_GROUP_PANEL_FROZEN_TRAILING_ROW, PANEL_GROUP_FROZEN_GROUP_Z_INDEX, S2Event, SPLIT_LINE_WIDTH, + FrozenGroupAreaTypeMap, } from '../common/constant'; import type { SimpleBBox } from '../engine'; import { FrozenGroup } from '../group/frozen-group'; @@ -26,17 +21,26 @@ import { } from '../utils/grid'; import type { Indexes, PanelIndexes } from '../utils/indexes'; import { floor } from '../utils/math'; +import type { + FrozenGroupAreas, + FrozenGroups, +} from '../common/interface/frozen'; +import type { S2BaseFrozenOptions } from '../common'; import { BaseFacet } from './base-facet'; -import { Frame } from './header/frame'; import { Node } from './layout/node'; import { calculateFrozenCornerCells, calculateInViewIndexes, - getFrozenDataCellType, - getFrozenLeafNodesCount, + getFrozenGroupTypeByCell, splitInViewIndexesWithFrozen, translateGroup, } from './utils'; +import { + getFrozenColOffset, + getFrozenTrailingColOffset, + getFrozenTrailingRowOffset, + getScrollGroupClip, +} from './header/util'; /** * Defines the row freeze abstract standard interface @@ -44,57 +48,49 @@ import { export abstract class FrozenFacet extends BaseFacet { public rowOffsets: number[]; - public frozenGroupInfo = { - [FrozenGroupType.FROZEN_COL]: { + public frozenGroups: FrozenGroups; + + public frozenGroupAreas = { + [FrozenGroupArea.Col]: { width: 0, x: 0, range: [] as number[], }, - [FrozenGroupType.FROZEN_TRAILING_COL]: { + [FrozenGroupArea.TrailingCol]: { width: 0, x: 0, range: [] as number[], }, - [FrozenGroupType.FROZEN_ROW]: { + [FrozenGroupArea.Row]: { height: 0, y: 0, range: [] as number[], }, - [FrozenGroupType.FROZEN_TRAILING_ROW]: { + [FrozenGroupArea.TrailingRow]: { height: 0, y: 0, range: [] as number[], }, - } satisfies Record< - FrozenGroupType, - { - width?: number; - height?: number; - x?: number; - y?: number; - range: number[]; - } - >; + } satisfies FrozenGroupAreas; + + protected validFrozenOptions: Required; public panelScrollGroupIndexes: Indexes = [0, 0, 0, 0]; protected override initPanelGroups(): void { super.initPanelGroups(); - [ - this.frozenRowGroup, - this.frozenColGroup, - this.frozenTrailingRowGroup, - this.frozenTrailingColGroup, - this.frozenTopGroup, - this.frozenBottomGroup, - ] = [ - KEY_GROUP_PANEL_FROZEN_ROW, - KEY_GROUP_PANEL_FROZEN_COL, - KEY_GROUP_PANEL_FROZEN_TRAILING_ROW, - KEY_GROUP_PANEL_FROZEN_TRAILING_COL, - KEY_GROUP_PANEL_FROZEN_TOP, - KEY_GROUP_PANEL_FROZEN_BOTTOM, - ].map((name) => { + + /* init frozen groups */ + this.frozenGroups = [ + FrozenGroupType.Row, + FrozenGroupType.Col, + FrozenGroupType.TrailingRow, + FrozenGroupType.TrailingCol, + FrozenGroupType.TopLeft, + FrozenGroupType.TopRight, + FrozenGroupType.BottomLeft, + FrozenGroupType.BottomRight, + ].reduce((acc, name) => { const frozenGroup = new FrozenGroup({ name, zIndex: PANEL_GROUP_FROZEN_GROUP_Z_INDEX, @@ -102,69 +98,77 @@ export abstract class FrozenFacet extends BaseFacet { }); this.panelGroup.appendChild(frozenGroup); + acc[name] = frozenGroup; - return frozenGroup; - }); + return acc; + }, {} as FrozenGroups); } - protected getFrozenOptions() { - const colLength = this.getColLeafNodes().length; - const cellRange = this.getCellRange(); + /** + * 获取冻结数量结果,主要是针对 col top level 的结果 + */ + public getFrozenOptions() { + if (!this.validFrozenOptions) { + const colLength = this.getColLeafNodes().length; + const cellRange = this.getCellRange(); + + this.validFrozenOptions = getValidFrozenOptions( + this.spreadsheet.options.frozen!, + colLength, + cellRange.end - cellRange.start + 1, + ); + } - return getValidFrozenOptions( - this.spreadsheet.options.frozen!, - colLength, - cellRange.end - cellRange.start + 1, - ); + return this.validFrozenOptions; } public calculateFrozenGroupInfo() { - const { - colCount = 0, - rowCount = 0, - trailingColCount = 0, - trailingRowCount = 0, - } = this.getFrozenOptions(); + const { colCount, rowCount, trailingColCount, trailingRowCount } = + this.getFrozenOptions(); - const topLevelColNodes = this.getTopLevelColNodes(); const viewCellHeights = this.viewCellHeights; const cellRange = this.getCellRange(); - const { frozenCol, frozenTrailingCol, frozenRow, frozenTrailingRow } = - this.frozenGroupInfo; + const leafColNodes = this.getColLeafNodes(); if (colCount > 0) { - frozenCol.width = - topLevelColNodes[colCount - 1].x + topLevelColNodes[colCount - 1].width; - frozenCol.x = 0; - frozenCol.range = [0, colCount - 1]; + this.frozenGroupAreas[FrozenGroupArea.Col].width = + leafColNodes[colCount - 1].x + leafColNodes[colCount - 1].width; + this.frozenGroupAreas[FrozenGroupArea.Col].x = 0; + this.frozenGroupAreas[FrozenGroupArea.Col].range = [0, colCount - 1]; } if (rowCount > 0) { - frozenRow.height = + this.frozenGroupAreas[FrozenGroupArea.Row].height = viewCellHeights.getCellOffsetY(cellRange.start + rowCount) - viewCellHeights.getCellOffsetY(cellRange.start); - frozenRow.y = 0; - frozenRow.range = [cellRange.start, cellRange.start + rowCount - 1]; + this.frozenGroupAreas[FrozenGroupArea.Row].y = + viewCellHeights.getCellOffsetY(cellRange.start); + this.frozenGroupAreas[FrozenGroupArea.Row].range = [ + cellRange.start, + cellRange.start + rowCount - 1, + ]; } if (trailingColCount > 0) { - frozenTrailingCol.width = - topLevelColNodes[topLevelColNodes.length - 1].x - - topLevelColNodes[topLevelColNodes.length - trailingColCount].x + - topLevelColNodes[topLevelColNodes.length - 1].width; - frozenTrailingCol.x = this.panelBBox.width - frozenTrailingCol.width; - frozenTrailingCol.range = [ - topLevelColNodes.length - trailingColCount, - topLevelColNodes.length - 1, + this.frozenGroupAreas[FrozenGroupArea.TrailingCol].width = + leafColNodes[leafColNodes.length - 1].x - + leafColNodes[leafColNodes.length - trailingColCount].x + + leafColNodes[leafColNodes.length - 1].width; + this.frozenGroupAreas[FrozenGroupArea.TrailingCol].x = + leafColNodes[leafColNodes.length - trailingColCount].x; + this.frozenGroupAreas[FrozenGroupArea.TrailingCol].range = [ + leafColNodes.length - trailingColCount, + leafColNodes.length - 1, ]; } if (trailingRowCount > 0) { - frozenTrailingRow.height = + this.frozenGroupAreas[FrozenGroupArea.TrailingRow].height = viewCellHeights.getCellOffsetY(cellRange.end + 1) - viewCellHeights.getCellOffsetY(cellRange.end + 1 - trailingRowCount); - frozenTrailingRow.y = this.panelBBox.height - frozenTrailingRow.height; - frozenTrailingRow.range = [ + this.frozenGroupAreas[FrozenGroupArea.TrailingRow].y = + viewCellHeights.getCellOffsetY(cellRange.end + 1 - trailingRowCount); + this.frozenGroupAreas[FrozenGroupArea.TrailingRow].range = [ cellRange.end - trailingRowCount + 1, cellRange.end, ]; @@ -174,12 +178,8 @@ export abstract class FrozenFacet extends BaseFacet { protected getFinalViewport() { const { viewportHeight: height, viewportWidth: width } = this.panelBBox; - const { - colCount = 0, - rowCount = 0, - trailingColCount = 0, - trailingRowCount = 0, - } = this.getFrozenOptions(); + const { colCount, rowCount, trailingColCount, trailingRowCount } = + this.getFrozenOptions(); const finalViewport: SimpleBBox = { width, @@ -189,25 +189,26 @@ export abstract class FrozenFacet extends BaseFacet { }; if (colCount > 0 || trailingColCount > 0) { - const { frozenTrailingCol, frozenCol } = this.frozenGroupInfo; - - finalViewport.width -= frozenTrailingCol.width! + frozenCol.width!; - finalViewport.x += frozenCol.width!; + finalViewport.width -= + this.frozenGroupAreas[FrozenGroupArea.Col].width! + + this.frozenGroupAreas[FrozenGroupArea.TrailingCol].width!; + finalViewport.x += this.frozenGroupAreas[FrozenGroupArea.Col].width!; } if (rowCount > 0 || trailingRowCount > 0) { - const { frozenRow, frozenTrailingRow } = this.frozenGroupInfo; - // canvas 高度小于 row height 和 trailingRow height 的时候 height 为 0 if ( finalViewport.height < - frozenRow.height! + frozenTrailingRow.height! + this.frozenGroupAreas[FrozenGroupArea.Row].height! + + this.frozenGroupAreas[FrozenGroupArea.TrailingRow].height! ) { finalViewport.height = 0; finalViewport.y = 0; } else { - finalViewport.height -= frozenRow.height! + frozenTrailingRow.height!; - finalViewport.y += frozenRow.height!; + finalViewport.height -= + this.frozenGroupAreas[FrozenGroupArea.Row].height! + + this.frozenGroupAreas[FrozenGroupArea.TrailingRow].height!; + finalViewport.y += this.frozenGroupAreas[FrozenGroupArea.Row].height!; } } @@ -215,16 +216,6 @@ export abstract class FrozenFacet extends BaseFacet { } public calculateXYIndexes(scrollX: number, scrollY: number): PanelIndexes { - const colLength = this.getColLeafNodes().length; - const cellRange = this.getCellRange(); - - const { - colCount = 0, - rowCount = 0, - trailingColCount = 0, - trailingRowCount = 0, - } = this.getFrozenOptions(); - const finalViewport: SimpleBBox = this.getFinalViewport(); const indexes = @@ -241,55 +232,32 @@ export abstract class FrozenFacet extends BaseFacet { this.panelScrollGroupIndexes = indexes; - const { colCount: realColCount, trailingColCount: realTrailingColCount } = - this.getRealFrozenColumns(colCount, trailingColCount); + const colLength = this.getColLeafNodes().length; + const cellRange = this.getCellRange(); return splitInViewIndexesWithFrozen( indexes, - { - colCount: realColCount, - trailingColCount: realTrailingColCount, - rowCount, - trailingRowCount, - }, + this.getFrozenOptions(), colLength, cellRange, ); } addDataCell = (cell: DataCell) => { - const { - rowCount = 0, - colCount = 0, - trailingRowCount = 0, - trailingColCount = 0, - } = this.getFrozenOptions(); - - const colLength = this.getColNodes().length; + const colLeafLength = this.getColLeafNodes().length; const cellRange = this.getCellRange(); - const { colCount: realColCount, trailingColCount: realTrailingColCount } = - this.getRealFrozenColumns(colCount, trailingColCount); - const frozenCellType = getFrozenDataCellType( + const frozenGroupType = getFrozenGroupTypeByCell( cell.getMeta(), - { - rowCount, - trailingRowCount, - colCount: realColCount, - trailingColCount: realTrailingColCount, - }, - colLength, + this.getFrozenOptions(), + colLeafLength, cellRange, ); - const groupName = FrozenCellGroupMap[frozenCellType]; - - if (groupName) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const group = this[groupName] as Group; - - group.appendChild(cell); + if (frozenGroupType === FrozenGroupType.Scroll) { + this.panelScrollGroup.appendChild(cell); + } else { + this.frozenGroups[frozenGroupType].appendChild(cell); } setTimeout(() => { @@ -314,12 +282,12 @@ export abstract class FrozenFacet extends BaseFacet { protected updateFrozenGroupGrid(): void { [ - FrozenGroupType.FROZEN_COL, - FrozenGroupType.FROZEN_ROW, - FrozenGroupType.FROZEN_TRAILING_COL, - FrozenGroupType.FROZEN_TRAILING_ROW, + FrozenGroupArea.Col, + FrozenGroupArea.Row, + FrozenGroupArea.TrailingCol, + FrozenGroupArea.TrailingRow, ].forEach((key) => { - if (!this.frozenGroupInfo[key].range) { + if (!this.frozenGroupAreas[key].range) { return; } @@ -327,14 +295,13 @@ export abstract class FrozenFacet extends BaseFacet { let rows: number[] = []; if (key.toLowerCase().includes('row')) { - const [rowMin, rowMax] = this.frozenGroupInfo[key].range || []; + const [rowMin, rowMax] = this.frozenGroupAreas[key].range || []; cols = this.gridInfo.cols; rows = getRowsForGrid(rowMin, rowMax, this.viewCellHeights); - if (key === FrozenGroupType.FROZEN_TRAILING_ROW) { - const top = - this.frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_ROW].y; + if (key === FrozenGroupArea.TrailingRow) { + const top = this.frozenGroupAreas[FrozenGroupArea.TrailingRow].y; rows = getFrozenRowsForGrid( rowMin, @@ -344,19 +311,21 @@ export abstract class FrozenFacet extends BaseFacet { ); } } else { - const [colMin, colMax] = this.frozenGroupInfo[key].range || []; - const nodes = this.getTopLevelColNodes(); + const [colMin, colMax] = this.frozenGroupAreas[key].range || []; + const nodes = this.getColLeafNodes(); cols = getColsForGrid(colMin, colMax, nodes); rows = this.gridInfo.rows; } - this[`${key}Group`].updateGrid( + const frozenGroup = FrozenGroupAreaTypeMap[key]; + + this.frozenGroups[frozenGroup].updateGrid( { cols, rows, }, - `${key}Group`, + frozenGroup, ); }); } @@ -373,7 +342,7 @@ export abstract class FrozenFacet extends BaseFacet { ) { super.translateRelatedGroups(scrollX, scrollY, hRowScroll); this.translateFrozenGroups(); - this.updateRowResizeArea(); + this.renderRowResizeArea(); this.renderFrozenGroupSplitLine(scrollX, scrollY); } @@ -381,43 +350,79 @@ export abstract class FrozenFacet extends BaseFacet { const { scrollY, scrollX } = this.getScrollOffset(); const paginationScrollY = this.getPaginationScrollY(); - const { x, y } = this.panelBBox; + const { x, y, viewportWidth, viewportHeight } = this.panelBBox; + + const colOffset = getFrozenColOffset(this, this.cornerBBox.width, scrollX); + + const trailingColOffset = getFrozenTrailingColOffset(this, viewportWidth); + + const trailingRowOffset = getFrozenTrailingRowOffset( + this, + viewportHeight, + paginationScrollY, + ); + + translateGroup( + this.frozenGroups[FrozenGroupType.TopLeft], + x - colOffset, + y - paginationScrollY, + ); + + translateGroup( + this.frozenGroups[FrozenGroupType.TopRight], + x - trailingColOffset, + y - paginationScrollY, + ); + + translateGroup( + this.frozenGroups[FrozenGroupType.BottomLeft], + x - colOffset, + y - trailingRowOffset, + ); - translateGroup(this.frozenTopGroup, x, y - paginationScrollY); - translateGroup(this.frozenBottomGroup, x, y); + translateGroup( + this.frozenGroups[FrozenGroupType.BottomRight], + x - trailingColOffset, + y - trailingRowOffset, + ); - translateGroup(this.frozenRowGroup, x - scrollX, y - paginationScrollY); - translateGroup(this.frozenTrailingRowGroup, x - scrollX, y); + translateGroup( + this.frozenGroups[FrozenGroupType.Row], + x - scrollX, + y - paginationScrollY, + ); + translateGroup( + this.frozenGroups[FrozenGroupType.TrailingRow], + x - scrollX, + y - trailingRowOffset, + ); - translateGroup(this.frozenColGroup, x, y - scrollY - paginationScrollY); translateGroup( - this.frozenTrailingColGroup, - x, + this.frozenGroups[FrozenGroupType.Col], + x - colOffset, + y - scrollY - paginationScrollY, + ); + translateGroup( + this.frozenGroups[FrozenGroupType.TrailingCol], + x - trailingColOffset, y - scrollY - paginationScrollY, ); }; - protected updateRowResizeArea() {} + protected renderRowResizeArea() {} // eslint-disable-next-line max-lines-per-function protected renderFrozenGroupSplitLine = (scrollX: number, scrollY: number) => { const { - width: panelWidth, - height: panelHeight, viewportWidth, viewportHeight, x: panelBBoxStartX, y: panelBBoxStartY, } = this.panelBBox; - const topLevelColNodes = this.getTopLevelColNodes(); const cellRange = this.getCellRange(); - const { - rowCount: frozenRowCount = 0, - colCount: frozenColCount = 0, - trailingColCount: frozenTrailingColCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - } = this.getFrozenOptions(); + const { rowCount, colCount, trailingColCount, trailingRowCount } = + this.getFrozenOptions(); // 在分页条件下需要额外处理 Y 轴滚动值 const relativeScrollY = Math.floor(scrollY - this.getPaginationScrollY()); @@ -456,36 +461,33 @@ export abstract class FrozenFacet extends BaseFacet { opacity: splitLine?.horizontalBorderColorOpacity, }; - const frameVerticalBorderWidth = Frame.getVerticalBorderWidth( - this.spreadsheet, - ); - - if (frozenColCount > 0) { - const x = topLevelColNodes.reduce((prev, item, idx) => { - if (idx < frozenColCount) { - return prev + item.width; - } - - return prev; - }, 0); + if (colCount > 0) { + const cornerWidth = this.cornerBBox.width; + const colOffset = getFrozenColOffset(this, cornerWidth, scrollX); + const x = + panelBBoxStartX + + this.frozenGroupAreas[FrozenGroupArea.Col].width - + colOffset; - const height = - (frozenTrailingRowCount > 0 ? panelHeight : viewportHeight) + - panelBBoxStartY; + const height = viewportHeight + panelBBoxStartY; renderLine(splitLineGroup, { ...verticalBorderStyle, - x1: x + panelBBoxStartX, - x2: x + panelBBoxStartX, + x1: x, + x2: x, y1: 0, y2: height, }); - if (splitLine?.showShadow && scrollX > 0) { + if ( + splitLine?.showShadow && + scrollX > 0 && + (this.spreadsheet.isFrozenRowHeader() || colOffset >= cornerWidth) + ) { splitLineGroup.appendChild( new Rect({ style: { - x: x + panelBBoxStartX, + x, y: 0, width: splitLine?.shadowWidth!, height, @@ -496,81 +498,75 @@ export abstract class FrozenFacet extends BaseFacet { } } - if (frozenRowCount > 0) { - const y = - panelBBoxStartY + - this.getTotalHeightForRange( - cellRange.start, - cellRange.start + frozenRowCount - 1, - ); - const width = frozenTrailingColCount > 0 ? panelWidth : viewportWidth; + if (trailingColCount > 0) { + const x = + viewportWidth - + this.frozenGroupAreas[FrozenGroupArea.TrailingCol].width + + panelBBoxStartX; + + const height = viewportHeight + panelBBoxStartY; renderLine(splitLineGroup, { - ...horizontalBorderStyle, - x1: 0, - x2: width + frameVerticalBorderWidth, - y1: y, - y2: y, + ...verticalBorderStyle, + x1: x, + x2: x, + y1: 0, + y2: height, }); - if (splitLine?.showShadow && relativeScrollY > 0) { + if (splitLine?.showShadow && floor(scrollX) < floor(maxScrollX)) { splitLineGroup.appendChild( new Rect({ style: { - x: 0, - y, - width: width + frameVerticalBorderWidth, - height: splitLine?.shadowWidth!, - fill: this.getShadowFill(90), + x: x - splitLine.shadowWidth!, + y: 0, + width: splitLine.shadowWidth!, + height, + fill: this.getShadowFill(180), }, }), ); } } - if (frozenTrailingColCount > 0) { - const { x } = - topLevelColNodes[topLevelColNodes.length - frozenTrailingColCount]; - const height = - (frozenTrailingRowCount ? panelHeight : viewportHeight) + - panelBBoxStartY; + if (rowCount > 0) { + const y = + panelBBoxStartY + this.frozenGroupAreas[FrozenGroupArea.Row].height; + const width = panelBBoxStartX + viewportWidth; renderLine(splitLineGroup, { - ...verticalBorderStyle, - x1: x + panelBBoxStartX, - x2: x + panelBBoxStartX, - y1: 0, - y2: height, + ...horizontalBorderStyle, + x1: 0, + x2: width, + y1: y, + y2: y, }); - if (splitLine?.showShadow && floor(scrollX) < floor(maxScrollX)) { + if (splitLine?.showShadow && relativeScrollY > 0) { splitLineGroup.appendChild( new Rect({ style: { - x: x + panelBBoxStartX - splitLine.shadowWidth!, - y: 0, - width: splitLine.shadowWidth!, - height, - fill: this.getShadowFill(180), + x: 0, + y, + width, + height: splitLine?.shadowWidth!, + fill: this.getShadowFill(90), }, }), ); } } - if (frozenTrailingRowCount > 0) { + if (trailingRowCount > 0) { const y = this.panelBBox.maxY - - this.getTotalHeightForRange( - cellRange.end - frozenTrailingRowCount + 1, - cellRange.end, - ); - const width = frozenTrailingColCount > 0 ? panelWidth : viewportWidth; + this.frozenGroupAreas[FrozenGroupArea.TrailingRow].height; + const width = panelBBoxStartX + viewportWidth; renderLine(splitLineGroup, { ...horizontalBorderStyle, x1: 0, - x2: width + frameVerticalBorderWidth, + x2: width, y1: y, y2: y, }); @@ -581,7 +577,7 @@ export abstract class FrozenFacet extends BaseFacet { style: { x: 0, y: y - splitLine.shadowWidth!, - width: width + frameVerticalBorderWidth, + width, height: splitLine.shadowWidth!, fill: this.getShadowFill(270), }, @@ -598,46 +594,25 @@ export abstract class FrozenFacet extends BaseFacet { } protected override getCenterFrameScrollX(scrollX: number): number { - if (this.getFrozenOptions().colCount! > 0) { - return 0; + if (this.getFrozenOptions().colCount > 0) { + return getFrozenColOffset(this, this.cornerBBox.width, scrollX); } return super.getCenterFrameScrollX(scrollX); } protected renderFrozenPanelCornerGroup = () => { - const topLevelNodes = this.getTopLevelColNodes(); const cellRange = this.getCellRange(); - const { - rowCount: frozenRowCount = 0, - colCount: frozenColCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - trailingColCount: frozenTrailingColCount = 0, - } = this.getFrozenOptions(); - - const { colCount, trailingColCount } = getFrozenLeafNodesCount( - topLevelNodes, - frozenColCount, - frozenTrailingColCount, - ); - const result = calculateFrozenCornerCells( - { - rowCount: frozenRowCount, - colCount, - trailingRowCount: frozenTrailingRowCount, - trailingColCount, - }, + this.getFrozenOptions(), this.getColLeafNodes().length, cellRange, ); - Object.keys(result).forEach((key) => { + (Object.keys(result) as (keyof typeof result)[]).forEach((key) => { const cells = result[key]; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const group = this[FrozenCellGroupMap[key]] as Group; + const group = this.frozenGroups[key]; if (group) { cells.forEach((cell) => { @@ -647,22 +622,6 @@ export abstract class FrozenFacet extends BaseFacet { }); }; - getRealFrozenColumns = ( - colCount: number, - trailingColCount: number, - ): { colCount: number; trailingColCount: number } => { - if (colCount || trailingColCount) { - const nodes = this.getTopLevelColNodes(); - - return getFrozenLeafNodesCount(nodes, colCount, trailingColCount); - } - - return { - colCount, - trailingColCount, - }; - }; - public getTotalHeightForRange = (start: number, end: number) => { if (start < 0 || end < 0) { return 0; @@ -694,29 +653,26 @@ export abstract class FrozenFacet extends BaseFacet { }; protected clip() { - const { frozenGroupInfo, spreadsheet } = this; + const { scrollX } = this.getScrollOffset(); - const isFrozenRowHeader = spreadsheet.isFrozenRowHeader(); + const { x: panelScrollGroupClipX, width: panelScrollGroupClipWidth } = + getScrollGroupClip(this, this.panelBBox); const frozenColGroupWidth = - frozenGroupInfo[FrozenGroupType.FROZEN_COL].width; + this.frozenGroupAreas[FrozenGroupArea.Col].width; const frozenTrailingColWidth = - frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_COL].width; + this.frozenGroupAreas[FrozenGroupArea.TrailingCol].width; const frozenRowGroupHeight = - frozenGroupInfo[FrozenGroupType.FROZEN_ROW].height; + this.frozenGroupAreas[FrozenGroupArea.Row].height; const frozenTrailingRowHeight = - frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_ROW].height; + this.frozenGroupAreas[FrozenGroupArea.TrailingRow].height; - const panelScrollGroupClipX = - (isFrozenRowHeader ? this.panelBBox.x : 0) + frozenColGroupWidth; const panelScrollGroupClipY = this.panelBBox.y + frozenRowGroupHeight; - const panelScrollGroupClipWidth = - this.panelBBox.width - - frozenColGroupWidth - - frozenTrailingColWidth + - (isFrozenRowHeader ? 0 : this.panelBBox.x); + const panelScrollGroupClipHeight = - this.panelBBox.height - frozenRowGroupHeight - frozenTrailingRowHeight; + this.panelBBox.viewportHeight - + frozenRowGroupHeight - + frozenTrailingRowHeight; this.panelScrollGroup.style.clipPath = new Rect({ style: { @@ -727,25 +683,31 @@ export abstract class FrozenFacet extends BaseFacet { }, }); - this.frozenColGroup.style.clipPath = new Rect({ + /* frozen groups clip */ + this.frozenGroups[FrozenGroupType.Col].style.clipPath = new Rect({ style: { - x: this.panelBBox.x, + x: + this.panelBBox.x - + getFrozenColOffset(this, this.cornerBBox.width, scrollX), y: panelScrollGroupClipY, width: frozenColGroupWidth, height: panelScrollGroupClipHeight, }, }); - this.frozenTrailingColGroup.style.clipPath = new Rect({ + this.frozenGroups[FrozenGroupType.TrailingCol].style.clipPath = new Rect({ style: { - x: this.panelBBox.x + this.panelBBox.width - frozenTrailingColWidth, + x: + this.panelBBox.x + + this.panelBBox.viewportWidth - + frozenTrailingColWidth, y: panelScrollGroupClipY, width: frozenTrailingColWidth, height: panelScrollGroupClipHeight, }, }); - this.frozenRowGroup.style.clipPath = new Rect({ + this.frozenGroups[FrozenGroupType.Row].style.clipPath = new Rect({ style: { x: panelScrollGroupClipX, y: this.panelBBox.y, @@ -754,10 +716,13 @@ export abstract class FrozenFacet extends BaseFacet { }, }); - this.frozenTrailingRowGroup.style.clipPath = new Rect({ + this.frozenGroups[FrozenGroupType.TrailingRow].style.clipPath = new Rect({ style: { x: panelScrollGroupClipX, - y: this.panelBBox.y + this.panelBBox.height - frozenTrailingRowHeight, + y: + this.panelBBox.y + + this.panelBBox.viewportHeight - + frozenTrailingRowHeight, width: panelScrollGroupClipWidth, height: frozenTrailingRowHeight, }, diff --git a/packages/s2-core/src/facet/header/base.ts b/packages/s2-core/src/facet/header/base.ts index fd678047be..ed1eb601c9 100644 --- a/packages/s2-core/src/facet/header/base.ts +++ b/packages/s2-core/src/facet/header/base.ts @@ -6,9 +6,19 @@ import type { BaseHeaderConfig } from './interface'; /** * New Base Header for all headers(cornerHeader, rowHeader, colHeader) * NOTE: Do not use this.cfg(which lays in group) to get header config, - * use {@see headerConfig} instead + * use @see headerConfig instead */ export abstract class BaseHeader extends Group { + protected scrollGroup: Group; + + protected frozenGroup: Group; + + protected frozenTrailingGroup: Group; + + protected extraFrozenNodes: Node[]; + + protected abstract initGroups(): void; + // header all cells layout protected abstract layout(): void; @@ -23,26 +33,16 @@ export abstract class BaseHeader extends Group { // protected offset: number; protected headerConfig: T; - protected constructor(config: T) { + constructor(config: T) { super(); this.headerConfig = config; + this.initGroups(); } public getHeaderConfig() { return this.headerConfig || ({} as T); } - /** - * 清空热区,为重绘做准备,防止热区重复渲染 - * @param type 当前重绘的header类型 - */ - protected clearResizeAreaGroup(type: string) { - const foregroundGroup = this.parentNode as Group; - const resizerGroup = foregroundGroup?.getElementById(type); - - resizerGroup?.removeChildren(); - } - // start render header public render(type: string): void { // clear resize group @@ -79,8 +79,21 @@ export abstract class BaseHeader extends Group { this.render(type); } + /** + * 清空热区,为重绘做准备,防止热区重复渲染 + * @param type 当前重绘的header类型 + */ + protected clearResizeAreaGroup(type: string) { + const foregroundGroup = this.parentNode as Group; + const resizerGroup = foregroundGroup?.getElementById(type); + + resizerGroup?.removeChildren(); + } + public clear() { - super.removeChildren(); + this.scrollGroup?.removeChildren(); + this.frozenGroup?.removeChildren(); + this.frozenTrailingGroup?.removeChildren(); } /** @@ -99,4 +112,10 @@ export abstract class BaseHeader extends Group { }) => cellPosition + cellSize >= viewportPosition && viewportPosition + viewportSize >= cellPosition; + + public getNodes(): Node[] { + const { nodes } = this.getHeaderConfig(); + + return nodes || []; + } } diff --git a/packages/s2-core/src/facet/header/col.ts b/packages/s2-core/src/facet/header/col.ts index 3fd284c6a5..f90b26ab76 100644 --- a/packages/s2-core/src/facet/header/col.ts +++ b/packages/s2-core/src/facet/header/col.ts @@ -2,24 +2,67 @@ import { Group, Rect } from '@antv/g'; import { each } from 'lodash'; import { ColCell } from '../../cell/col-cell'; import { + FRONT_GROUND_GROUP_FROZEN_Z_INDEX, FRONT_GROUND_GROUP_SCROLL_Z_INDEX, + FrozenGroupArea, + KEY_GROUP_COL_FROZEN, + KEY_GROUP_COL_FROZEN_TRAILING, KEY_GROUP_COL_SCROLL, S2Event, } from '../../common/constant'; import type { Node } from '../layout/node'; import { translateGroupX } from '../utils'; +import type { FrozenFacet } from '../frozen-facet'; import { BaseHeader } from './base'; import type { ColHeaderConfig } from './interface'; +import { + getExtraFrozenColNodes, + getFrozenColOffset, + getFrozenTrailingColOffset, + getScrollGroupClip, +} from './util'; /** * Column Header for SpreadSheet */ export class ColHeader extends BaseHeader { - protected scrollGroup: Group; + protected initGroups(): void { + this.scrollGroup = this.appendChild( + new Group({ + name: KEY_GROUP_COL_SCROLL, + style: { zIndex: FRONT_GROUND_GROUP_SCROLL_Z_INDEX }, + }), + ); + + this.frozenGroup = this.appendChild( + new Group({ + name: KEY_GROUP_COL_FROZEN, + style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, + }), + ); + + this.frozenTrailingGroup = this.appendChild( + new Group({ + name: KEY_GROUP_COL_FROZEN_TRAILING, + style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, + }), + ); + + this.extraFrozenNodes = getExtraFrozenColNodes( + this.headerConfig.spreadsheet.facet as FrozenFacet, + ); + } + + protected getCellGroup(node: Node): Group { + if (node.isFrozenHead) { + return this.frozenGroup; + } + + if (node.isFrozenTrailing) { + return this.frozenTrailingGroup; + } - constructor(config: ColHeaderConfig) { - super(config); - this.initScrollGroup(); + return this.scrollGroup; } protected getCellInstance(node: Node) { @@ -34,13 +77,31 @@ export class ColHeader extends BaseHeader { ); } - private initScrollGroup() { - this.scrollGroup = this.appendChild( - new Group({ - name: KEY_GROUP_COL_SCROLL, - style: { zIndex: FRONT_GROUND_GROUP_SCROLL_Z_INDEX }, - }), - ); + protected appendNode(node: Node) { + const { spreadsheet } = this.getHeaderConfig(); + const group = this.getCellGroup(node); + + const cell = this.getCellInstance(node); + + node.belongsCell = cell; + + group?.appendChild(cell); + spreadsheet.emit(S2Event.COL_CELL_RENDER, cell as ColCell); + spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); + } + + protected layout() { + const { nodes } = this.getHeaderConfig(); + + each(nodes, (node) => { + if (this.isColCellInRect(node)) { + this.appendNode(node); + } + }); + + each(this.extraFrozenNodes, (node) => { + this.appendNode(node); + }); } /** @@ -57,68 +118,103 @@ export class ColHeader extends BaseHeader { } protected clip() { - const { height, width, spreadsheet, position } = this.getHeaderConfig(); - const isFrozenRowHeader = spreadsheet.isFrozenRowHeader(); + const { + height, + spreadsheet, + position, + viewportWidth, + cornerWidth = 0, + scrollX = 0, + } = this.getHeaderConfig(); + + const facet = spreadsheet.facet as FrozenFacet; + const frozenGroupAreas = facet.frozenGroupAreas; + + const frozenColGroupWidth = frozenGroupAreas[FrozenGroupArea.Col].width; + const frozenTrailingColGroupWidth = + frozenGroupAreas[FrozenGroupArea.TrailingCol].width; + + const { x, width } = getScrollGroupClip(facet, position); this.scrollGroup.style.clipPath = new Rect({ style: { - x: isFrozenRowHeader ? position.x : 0, - y: isFrozenRowHeader ? position.y : 0, - width: isFrozenRowHeader ? width : position.x + width, + x, + y: position.y, + width, height, }, }); - } - public clear() { - this.scrollGroup?.removeChildren(); - } + this.frozenGroup.style.clipPath = new Rect({ + style: { + x: position.x - getFrozenColOffset(facet, cornerWidth, scrollX), + y: position.y, + width: frozenColGroupWidth, + height, + }, + }); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected getCellGroup(node: Node): Group { - return this.scrollGroup; + this.frozenTrailingGroup.style.clipPath = new Rect({ + style: { + x: position.x + viewportWidth - frozenTrailingColGroupWidth, + y: position.y, + width: frozenTrailingColGroupWidth, + height, + }, + }); } protected isColCellInRect(node: Node): boolean { const { spreadsheet, - cornerWidth, - width, + cornerWidth = 0, + viewportWidth, scrollX = 0, + position, } = this.getHeaderConfig(); - return ( - // don't care about scrollY, because there is only freeze col-header exist - width + scrollX > node.x && - scrollX - (spreadsheet.isFrozenRowHeader() ? 0 : cornerWidth!) < - node.x + node.width - ); - } - - protected layout() { - const { nodes, spreadsheet } = this.getHeaderConfig(); + const frozenGroupAreas = (spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; - each(nodes, (node) => { - if (this.isColCellInRect(node)) { - const group = this.getCellGroup(node); + let leftBoundary; - node.isFrozen = group !== this.scrollGroup; + const frozenColWidth = frozenGroupAreas[FrozenGroupArea.Col].width; + const frozenTrailingColWidth = + frozenGroupAreas[FrozenGroupArea.TrailingCol].width; - const cell = this.getCellInstance(node); - - node.belongsCell = cell; + if (spreadsheet.isFrozenRowHeader()) { + leftBoundary = frozenColWidth; + } else if (frozenColWidth) { + // 如果存在列冻结,那么 frame 最多滚动到最左侧,即滚动 cornerWidth 的宽度 + leftBoundary = frozenColWidth - cornerWidth; + } else { + leftBoundary = -position.x; + } - group?.appendChild(cell); - spreadsheet.emit(S2Event.COL_CELL_RENDER, cell as ColCell); - spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); - } - }); + return ( + // don't care about scrollY, because there is only freeze col-header exist + viewportWidth - frozenTrailingColWidth >= node.x - scrollX && + leftBoundary <= node.x + node.width - scrollX + ); } protected offset() { - const { position, scrollX = 0 } = this.getHeaderConfig(); + const { + viewportWidth, + scrollX = 0, + position, + spreadsheet, + cornerWidth, + } = this.getHeaderConfig(); - // 暂时不考虑移动 y translateGroupX(this.scrollGroup, position.x - scrollX); + + const facet = spreadsheet.facet as FrozenFacet; + const colOffset = getFrozenColOffset(facet, cornerWidth, scrollX); + + const trailingColOffset = getFrozenTrailingColOffset(facet, viewportWidth); + + translateGroupX(this.frozenGroup, position.x - colOffset); + translateGroupX(this.frozenTrailingGroup, position.x - trailingColOffset); } } diff --git a/packages/s2-core/src/facet/header/corner.ts b/packages/s2-core/src/facet/header/corner.ts index e82aa940f4..9f0be4b9a3 100644 --- a/packages/s2-core/src/facet/header/corner.ts +++ b/packages/s2-core/src/facet/header/corner.ts @@ -1,4 +1,4 @@ -import { Rect, type PointLike } from '@antv/g'; +import { Rect, type PointLike, Group } from '@antv/g'; import { includes } from 'lodash'; import { CornerCell } from '../../cell/corner-cell'; import { S2Event } from '../../common'; @@ -7,7 +7,11 @@ import type { CornerBBox } from '../bbox/corner-bbox'; import type { PanelBBox } from '../bbox/panel-bbox'; import { Node } from '../layout/node'; import { translateGroupX } from '../utils'; -import { getDefaultCornerText } from './../../common/constant/basic'; +import { + FRONT_GROUND_GROUP_SCROLL_Z_INDEX, + KEY_GROUP_CORNER_SCROLL, + getDefaultCornerText, +} from './../../common/constant/basic'; import { BaseHeader } from './base'; import type { BaseCornerOptions, CornerHeaderConfig } from './interface'; @@ -15,8 +19,13 @@ import type { BaseCornerOptions, CornerHeaderConfig } from './interface'; * Corner Header for SpreadSheet */ export class CornerHeader extends BaseHeader { - constructor(config: CornerHeaderConfig) { - super(config); + protected initGroups(): void { + this.scrollGroup = this.appendChild( + new Group({ + name: KEY_GROUP_CORNER_SCROLL, + style: { zIndex: FRONT_GROUND_GROUP_SCROLL_Z_INDEX }, + }), + ); } protected getCellInstance(node: Node): CornerCell { @@ -230,15 +239,7 @@ export class CornerHeader extends BaseHeader { this.render(type); } - public destroy(): void { - super.destroy(); - } - protected layout() { - this.renderCells(); - } - - protected renderCells() { const { nodes, spreadsheet } = this.getHeaderConfig(); const cornerHeader = spreadsheet.options?.cornerHeader; @@ -251,7 +252,7 @@ export class CornerHeader extends BaseHeader { nodes.forEach((node) => { const cell = this.getCellInstance(node); - this.appendChild(cell); + this.scrollGroup.appendChild(cell); spreadsheet.emit(S2Event.CORNER_CELL_RENDER, cell); spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); }); @@ -260,13 +261,13 @@ export class CornerHeader extends BaseHeader { protected offset() { const { scrollX = 0 } = this.getHeaderConfig(); - translateGroupX(this, -scrollX); + translateGroupX(this.scrollGroup, -scrollX); } protected clip(): void { const { width, height } = this.getHeaderConfig(); - this.style.clipPath = new Rect({ + this.scrollGroup.style.clipPath = new Rect({ style: { x: 0, y: 0, @@ -275,8 +276,4 @@ export class CornerHeader extends BaseHeader { }, }); } - - public getNodes(): Node[] { - return this.headerConfig.nodes || []; - } } diff --git a/packages/s2-core/src/facet/header/frame.ts b/packages/s2-core/src/facet/header/frame.ts index 158d9406ea..0c6f2cb680 100644 --- a/packages/s2-core/src/facet/header/frame.ts +++ b/packages/s2-core/src/facet/header/frame.ts @@ -4,6 +4,7 @@ import type { FrameConfig } from '../../common/interface'; import { translateGroup } from '../utils'; import type { SpreadSheet } from '../../sheet-type/spread-sheet'; import { floor } from '../../utils/math'; +import type { FrozenFacet } from '../frozen-facet'; export class Frame extends Group { declare cfg: FrameConfig; @@ -71,9 +72,14 @@ export class Frame extends Group { } public onChangeShadowVisibility(scrollX: number, maxScrollX: number) { - this.cfg.showViewportLeftShadow = scrollX > 0; + const { colCount, trailingColCount } = ( + this.cfg.spreadsheet.facet as FrozenFacet + ).getFrozenOptions(); + + this.cfg.showViewportLeftShadow = colCount === 0 && scrollX > 0; // baseFacet#renderHScrollBar render condition - this.cfg.showViewportRightShadow = floor(scrollX) < floor(maxScrollX); + this.cfg.showViewportRightShadow = + trailingColCount === 0 && floor(scrollX) < floor(maxScrollX); this.render(); } diff --git a/packages/s2-core/src/facet/header/row.ts b/packages/s2-core/src/facet/header/row.ts index 0d08e50847..61822644e1 100644 --- a/packages/s2-core/src/facet/header/row.ts +++ b/packages/s2-core/src/facet/header/row.ts @@ -1,34 +1,27 @@ import { Group, Rect } from '@antv/g'; import { each } from 'lodash'; -import { RowCell } from '../../cell'; +import { RowCell, SeriesNumberCell } from '../../cell'; import type { Node } from '../layout/node'; -import { getFrozenRowCfgPivot, translateGroup } from '../utils'; +import { translateGroup } from '../utils'; import { FRONT_GROUND_GROUP_FROZEN_Z_INDEX, FRONT_GROUND_GROUP_SCROLL_Z_INDEX, - FrozenGroupType, + FrozenGroupArea, KEY_GROUP_ROW_HEADER_FROZEN, + KEY_GROUP_ROW_HEADER_FROZEN_TRAILING, KEY_GROUP_ROW_SCROLL, S2Event, } from '../../common'; import type { FrozenFacet } from '../frozen-facet'; import { BaseHeader } from './base'; import type { RowHeaderConfig } from './interface'; +import { getExtraFrozenRowNodes, getFrozenTrailingRowOffset } from './util'; /** * Row Header for SpreadSheet */ export class RowHeader extends BaseHeader { - public scrollGroup: Group; - - public frozenRowGroup: Group; - - constructor(config: RowHeaderConfig) { - super(config); - this.initGroups(); - } - - private initGroups() { + protected initGroups(): void { this.scrollGroup = this.appendChild( new Group({ name: KEY_GROUP_ROW_SCROLL, @@ -36,15 +29,27 @@ export class RowHeader extends BaseHeader { }), ); - this.frozenRowGroup = this.appendChild( + this.frozenGroup = this.appendChild( new Group({ name: KEY_GROUP_ROW_HEADER_FROZEN, style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, }), ); + this.frozenTrailingGroup = this.appendChild( + new Group({ + name: KEY_GROUP_ROW_HEADER_FROZEN_TRAILING, + style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, + }), + ); + + const { spreadsheet } = this.getHeaderConfig(); + + this.extraFrozenNodes = getExtraFrozenRowNodes( + spreadsheet.facet as FrozenFacet, + ); } - public getCellInstance(node: Node): RowCell { + public getCellInstance(node: Node): RowCell | SeriesNumberCell { const headerConfig = this.getHeaderConfig(); const { spreadsheet } = headerConfig; @@ -57,97 +62,138 @@ export class RowHeader extends BaseHeader { } // row'cell only show when visible - protected isRowCellInRect(node: Node): boolean { + protected isCellInRect(node: Node): boolean { const { width, viewportHeight, position, scrollY = 0, scrollX = 0, + spreadsheet, } = this.getHeaderConfig(); - - if (this.isFrozenRow(node)) { - return true; - } + const frozenGroupAreas = (spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; return ( // bottom - viewportHeight + scrollY > node.y && + viewportHeight + + scrollY - + frozenGroupAreas[FrozenGroupArea.TrailingRow].height > + node.y && // top - scrollY < node.y + node.height && + scrollY + frozenGroupAreas[FrozenGroupArea.Row].height < + node.y + node.height && // left - width - position.x + scrollX > node.x && + width + scrollX - position.x > node.x && // right scrollX - position.x < node.x + node.width ); } - public isFrozenRow(node: Node): boolean { - const { spreadsheet } = this.headerConfig; - const { facet } = spreadsheet; - const { rowCount = 0 } = getFrozenRowCfgPivot( - spreadsheet.options, - facet.getRowNodes(), - ); - - return rowCount > 0 && node.rowIndex >= 0 && node.rowIndex < rowCount; - } - protected getCellGroup(item: Node): Group { - if (this.isFrozenRow(item)) { - return this.frozenRowGroup; + if (item.isFrozenHead) { + return this.frozenGroup; + } + + if (item.isFrozenTrailing) { + return this.frozenTrailingGroup; } return this.scrollGroup; } protected layout() { - const { nodes, spreadsheet } = this.getHeaderConfig(); + const { nodes } = this.getHeaderConfig(); - // row'cell only show when visible - each(nodes, (node) => { - if (this.isRowCellInRect(node) && node.height !== 0) { - const group = this.getCellGroup(node); + const appendNode = (node: Node) => { + const group = this.getCellGroup(node); + + const cell = this.getCellInstance(node); - node.isFrozen = group !== this.scrollGroup; + node.belongsCell = cell; - const cell = this.getCellInstance(node); + group.appendChild(cell); - node.belongsCell = cell; + this.emitRenderEvent(cell); + }; + + // row'cell only show when visible + each(nodes, (node) => { + if (this.isCellInRect(node) && node.height !== 0) { + appendNode(node); + } + }); - group.appendChild(cell); - spreadsheet.emit(S2Event.ROW_CELL_RENDER, cell); - spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); + each(this.extraFrozenNodes, (node) => { + if (node.height !== 0) { + appendNode(node); } }); } + protected emitRenderEvent(cell: RowCell | SeriesNumberCell) { + const { spreadsheet } = this.getHeaderConfig(); + + spreadsheet.emit(S2Event.ROW_CELL_RENDER, cell as RowCell); + spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); + } + protected offset() { - const { scrollX = 0, scrollY = 0, position } = this.getHeaderConfig(); + const { + scrollX = 0, + scrollY = 0, + position, + spreadsheet, + viewportHeight, + } = this.getHeaderConfig(); const translateX = position.x - scrollX; + const paginationScrollY = spreadsheet.facet.getPaginationScrollY(); + + const facet = spreadsheet.facet as FrozenFacet; + + const trailingRowOffset = getFrozenTrailingRowOffset( + facet, + viewportHeight, + paginationScrollY, + ); + translateGroup(this.scrollGroup, translateX, position.y - scrollY); - translateGroup(this.frozenRowGroup, translateX, position.y); + translateGroup( + this.frozenGroup, + translateX, + position.y - paginationScrollY, + ); + translateGroup( + this.frozenTrailingGroup, + translateX, + position.y - trailingRowOffset, + ); } protected clip(): void { const { width, viewportHeight, position, spreadsheet } = this.getHeaderConfig(); - const frozenRowGroupHeight = (spreadsheet.facet as FrozenFacet) - .frozenGroupInfo[FrozenGroupType.FROZEN_ROW].height; + const frozenGroupAreas = (spreadsheet.facet as FrozenFacet) + .frozenGroupAreas; + + const frozenRowGroupHeight = frozenGroupAreas[FrozenGroupArea.Row].height; + const frozenTrailingRowGroupHeight = + frozenGroupAreas[FrozenGroupArea.TrailingRow].height; this.scrollGroup.style.clipPath = new Rect({ style: { x: spreadsheet.facet.cornerBBox.x, y: position.y + frozenRowGroupHeight, width, - height: viewportHeight, + height: + viewportHeight - frozenRowGroupHeight - frozenTrailingRowGroupHeight, }, }); - this.frozenRowGroup.style.clipPath = new Rect({ + this.frozenGroup.style.clipPath = new Rect({ style: { x: spreadsheet.facet.cornerBBox.x, y: position.y, @@ -155,10 +201,14 @@ export class RowHeader extends BaseHeader { height: frozenRowGroupHeight, }, }); - } - public clear() { - this.scrollGroup?.removeChildren(); - this.frozenRowGroup?.removeChildren(); + this.frozenTrailingGroup.style.clipPath = new Rect({ + style: { + x: spreadsheet.facet.cornerBBox.x, + y: position.y + viewportHeight - frozenTrailingRowGroupHeight, + width, + height: frozenTrailingRowGroupHeight, + }, + }); } } diff --git a/packages/s2-core/src/facet/header/series-number.ts b/packages/s2-core/src/facet/header/series-number.ts index c9d9d1398d..b6b644ff89 100644 --- a/packages/s2-core/src/facet/header/series-number.ts +++ b/packages/s2-core/src/facet/header/series-number.ts @@ -1,32 +1,22 @@ -import { Rect } from '@antv/g'; -import { each } from 'lodash'; +import { Group } from '@antv/g'; import { SeriesNumberCell } from '../../cell/series-number-cell'; import type { SpreadSheet } from '../../sheet-type/index'; import type { PanelBBox } from '../bbox/panel-bbox'; import type { Hierarchy } from '../layout/hierarchy'; import type { Node } from '../layout/node'; -import { translateGroup } from '../utils'; -import { S2Event } from '../../common'; -import { BaseHeader } from './base'; -import type { BaseHeaderConfig } from './interface'; -import { getSeriesNumberNodes } from './util'; - -export class SeriesNumberHeader extends BaseHeader { - constructor(config: BaseHeaderConfig) { - super(config); - } - - protected getCellInstance(node: Node) { - const headerConfig = this.getHeaderConfig(); - const { spreadsheet } = headerConfig; - const { seriesNumberCell } = spreadsheet.options; - - return ( - seriesNumberCell?.(node, spreadsheet, headerConfig) || - new SeriesNumberCell(node, spreadsheet, headerConfig) - ); - } - +import { + FRONT_GROUND_GROUP_FROZEN_Z_INDEX, + FRONT_GROUND_GROUP_SCROLL_Z_INDEX, + KEY_GROUP_ROW_INDEX_FROZEN, + KEY_GROUP_ROW_INDEX_FROZEN_TRAILING, + KEY_GROUP_ROW_INDEX_SCROLL, + S2Event, +} from '../../common'; +import type { FrozenFacet } from '../frozen-facet'; +import { getExtraFrozenSeriesNodes, getSeriesNumberNodes } from './util'; +import { RowHeader } from './row'; + +export class SeriesNumberHeader extends RowHeader { /** * Get seriesNumber header by config */ @@ -64,59 +54,50 @@ export class SeriesNumberHeader extends BaseHeader { }); } - public clip(): void { - const { width, viewportHeight, position, spreadsheet } = - this.getHeaderConfig(); - - this.style.clipPath = new Rect({ - style: { - x: spreadsheet.facet.cornerBBox.x, - y: position.y, - width, - height: viewportHeight, - }, - }); - } - - public layout() { - const { - nodes, - scrollY = 0, - viewportHeight, - spreadsheet, - } = this.getHeaderConfig(); - - each(nodes, (node) => { - const { y, height: cellHeight } = node; - const isHeaderCellInViewport = this.isHeaderCellInViewport({ - cellPosition: y, - cellSize: cellHeight, - viewportPosition: scrollY, - viewportSize: viewportHeight, - }); + protected initGroups(): void { + this.scrollGroup = this.appendChild( + new Group({ + name: KEY_GROUP_ROW_INDEX_SCROLL, + style: { zIndex: FRONT_GROUND_GROUP_SCROLL_Z_INDEX }, + }), + ); - if (!isHeaderCellInViewport) { - return; - } + this.frozenGroup = this.appendChild( + new Group({ + name: KEY_GROUP_ROW_INDEX_FROZEN, + style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, + }), + ); + this.frozenTrailingGroup = this.appendChild( + new Group({ + name: KEY_GROUP_ROW_INDEX_FROZEN_TRAILING, + style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, + }), + ); - const cell = this.getCellInstance(node); + const { spreadsheet, nodes } = this.getHeaderConfig(); - node.belongsCell = cell; - this.appendChild(cell); - spreadsheet.emit(S2Event.SERIES_NUMBER_CELL_RENDER, cell); - spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); - }); + this.extraFrozenNodes = getExtraFrozenSeriesNodes( + spreadsheet.facet as FrozenFacet, + nodes, + ); } - protected offset() { - const { scrollY = 0, scrollX = 0, position } = this.getHeaderConfig(); + getCellInstance(node: Node) { + const headerConfig = this.getHeaderConfig(); + const { spreadsheet } = headerConfig; + const { seriesNumberCell } = spreadsheet.options; - translateGroup(this, position.x - scrollX, position.y - scrollY); + return ( + seriesNumberCell?.(node, spreadsheet, headerConfig) || + new SeriesNumberCell(node, spreadsheet, headerConfig) + ); } - public getNodes(): Node[] { - const { nodes } = this.getHeaderConfig(); + protected emitRenderEvent(cell: SeriesNumberCell): void { + const { spreadsheet } = this.getHeaderConfig(); - return nodes || []; + spreadsheet.emit(S2Event.SERIES_NUMBER_CELL_RENDER, cell); + spreadsheet.emit(S2Event.LAYOUT_CELL_RENDER, cell); } } diff --git a/packages/s2-core/src/facet/header/table-col.ts b/packages/s2-core/src/facet/header/table-col.ts index 0588b647cc..9235cd353c 100644 --- a/packages/s2-core/src/facet/header/table-col.ts +++ b/packages/s2-core/src/facet/header/table-col.ts @@ -1,23 +1,7 @@ -import { Group, Rect, type RectStyleProps } from '@antv/g'; -import { TableColCell, TableCornerCell, type ColCell } from '../../cell'; -import { - FRONT_GROUND_GROUP_FROZEN_Z_INDEX, - FrozenGroupType, - KEY_GROUP_COL_FROZEN, - KEY_GROUP_COL_FROZEN_TRAILING, - KEY_GROUP_FROZEN_COL_RESIZE_AREA, - SERIES_NUMBER_FIELD, -} from '../../common/constant'; +import { ColCell, TableColCell, TableCornerCell } from '../../cell'; +import { SERIES_NUMBER_FIELD } from '../../common/constant'; import type { SpreadSheet } from '../../sheet-type'; import type { Node } from '../layout/node'; -import { - getFrozenLeafNodesCount, - getLeftLeafNode, - isFrozenCol, - isFrozenTrailingCol, - translateGroupX, -} from '../utils'; -import type { FrozenFacet } from '../frozen-facet'; import { ColHeader } from './col'; import type { ColHeaderConfig } from './interface'; @@ -25,21 +9,6 @@ import type { ColHeaderConfig } from './interface'; * Column Header for SpreadSheet */ export class TableColHeader extends ColHeader { - public frozenColGroup: Group; - - public frozenTrailingColGroup: Group; - - private finalColCount: number; - - private finalTrailingColCount: number; - - private topLevelColNodeLength: number; - - constructor(config: ColHeaderConfig) { - super(config); - this.initFrozenColGroups(); - } - protected getCellInstance(node: Node) { const headerConfig = this.getHeaderConfig(); const { spreadsheet } = headerConfig; @@ -58,130 +27,4 @@ export class TableColHeader extends ColHeader { return colCell?.(...args) || new TableColCell(...args); } - - private initFrozenColGroups() { - const headerConfig = this.getHeaderConfig(); - const { - colCount: frozenColCount, - trailingColCount: frozenTrailingColCount, - } = headerConfig.spreadsheet.options.frozen!; - - const topLevelNodes = headerConfig.spreadsheet.facet.getColNodes(0); - const { colCount, trailingColCount } = getFrozenLeafNodesCount( - topLevelNodes, - frozenColCount!, - frozenTrailingColCount!, - ); - - this.finalColCount = colCount; - this.finalTrailingColCount = trailingColCount; - this.topLevelColNodeLength = topLevelNodes.length; - - if (colCount) { - this.frozenColGroup = this.appendChild( - new Group({ - name: KEY_GROUP_COL_FROZEN, - style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, - }), - ); - } - - if (trailingColCount) { - this.frozenTrailingColGroup = this.appendChild( - new Group({ - name: KEY_GROUP_COL_FROZEN_TRAILING, - style: { zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX }, - }), - ); - } - } - - public clear() { - super.clear(); - - this.frozenTrailingColGroup?.removeChildren(); - this.frozenColGroup?.removeChildren(); - - const { spreadsheet } = this.getHeaderConfig(); - // 额外清除冻结列的 Resizer Area - const resizerArea = - spreadsheet.facet?.foregroundGroup.getElementById( - KEY_GROUP_FROZEN_COL_RESIZE_AREA, - ); - - resizerArea?.removeChildren(); - } - - private getColFrozenOptionsByNode(node: Node) { - const leftLeafNode = getLeftLeafNode(node); - - return { - colLength: this.topLevelColNodeLength, - leftLeafNodeColIndex: leftLeafNode.colIndex, - colCount: this.finalColCount, - trailingColCount: this.finalTrailingColCount, - }; - } - - protected getCellGroup(node: Node): Group { - const { colLength, leftLeafNodeColIndex, colCount, trailingColCount } = - this.getColFrozenOptionsByNode(node); - - if (isFrozenCol(leftLeafNodeColIndex, colCount)) { - return this.frozenColGroup; - } - - if ( - isFrozenTrailingCol(leftLeafNodeColIndex, trailingColCount, colLength) - ) { - return this.frozenTrailingColGroup; - } - - return this.scrollGroup; - } - - protected isColCellInRect(node: Node): boolean { - const { leftLeafNodeColIndex, colLength, colCount, trailingColCount } = - this.getColFrozenOptionsByNode(node); - - if ( - isFrozenCol(leftLeafNodeColIndex, colCount) || - isFrozenTrailingCol(leftLeafNodeColIndex, trailingColCount, colLength) - ) { - return true; - } - - return super.isColCellInRect(node); - } - - public getScrollGroupClipBBox = (): RectStyleProps => { - const { width, height, spreadsheet, position } = this.getHeaderConfig(); - const frozenGroupInfo = (spreadsheet.facet as FrozenFacet).frozenGroupInfo; - const colWidth = frozenGroupInfo[FrozenGroupType.FROZEN_COL].width; - const trailingColWidth = - frozenGroupInfo[FrozenGroupType.FROZEN_TRAILING_COL].width; - const scrollGroupWidth = width - colWidth - trailingColWidth; - - return { - x: position.x + colWidth, - y: position.y, - width: scrollGroupWidth, - height, - }; - }; - - protected override offset() { - super.offset(); - - const { position } = this.getHeaderConfig(); - - translateGroupX(this.frozenColGroup, position.x); - translateGroupX(this.frozenTrailingColGroup, position.x); - } - - protected clip(): void { - this.scrollGroup.style.clipPath = new Rect({ - style: this.getScrollGroupClipBBox(), - }); - } } diff --git a/packages/s2-core/src/facet/header/util.ts b/packages/s2-core/src/facet/header/util.ts index dbcc00a796..dbe8340690 100644 --- a/packages/s2-core/src/facet/header/util.ts +++ b/packages/s2-core/src/facet/header/util.ts @@ -1,6 +1,12 @@ +import type { PointLike } from '@antv/g-lite'; +import { isNumber } from 'lodash'; +import { FrozenGroupArea, ROOT_NODE_ID } from '../../common'; import type { SpreadSheet } from '../../sheet-type'; +import type { FrozenFacet } from '../frozen-facet'; import type { Hierarchy } from '../layout/hierarchy'; import { Node } from '../layout/node'; +import type { AreaBBox } from '../../common/interface/frozen'; +import { Frame } from './frame'; export const getSeriesNumberNodes = ( rowsHierarchy: Hierarchy, @@ -26,8 +32,349 @@ export const getSeriesNumberNodes = ( ? node.getTotalHeightForTreeHierarchy() : node.height; sNode.isLeaf = true; + sNode.relatedNode = node; sNode.spreadsheet = spreadsheet; return sNode; }); }; + +const getAllParents = (nodes: Node[]) => { + const parents = nodes.reduce((pre, leaf) => { + let parent = leaf.parent; + + while (parent && parent.id !== ROOT_NODE_ID) { + // eslint-disable-next-line no-loop-func + if (!pre.find((node) => node.id === parent!.id)) { + pre.push(parent); + } + + parent = parent.parent; + } + + return pre; + }, [] as Node[]); + + return parents; +}; + +const clipFrozenRowHeadNode = ( + node: Node, + { y = 0, height = 0 }: Pick, +) => { + if (node.y < y) { + node.height -= y - node.y; + node.y = y; + } + + if (node.y + node.height > y + height) { + node.height -= node.y + node.height - y - height; + } +}; + +const clipFrozenTrailingRowHeadNode = ( + node: Node, + { y = 0, height = 0 }: Pick, +) => { + if (node.y + node.height > y + height) { + node.height -= node.y + node.height - y - height; + } + + if (node.y < y) { + node.height -= y - node.y; + node.y = y; + } +}; + +export const getExtraFrozenRowNodes = (facet: FrozenFacet) => { + const extraNodes: Node[] = []; + + const { start, end } = facet.getCellRange(); + const { rowCount, trailingRowCount } = facet.getFrozenOptions(); + + if (rowCount) { + const frozenLeafNodes = facet.getRowLeafNodesByRange( + start, + start + rowCount - 1, + )!; + + frozenLeafNodes.forEach((leafNode) => { + const newLeafNode = leafNode.clone(); + + newLeafNode.isFrozenHead = true; + extraNodes.push(newLeafNode); + }); + + const parents = getAllParents(frozenLeafNodes); + + parents.forEach((parent) => { + const newParent = parent.clone(); + + newParent.isFrozenHead = true; + + clipFrozenRowHeadNode( + newParent, + facet.frozenGroupAreas[FrozenGroupArea.Row], + ); + + extraNodes.push(newParent); + }); + } + + if (trailingRowCount) { + const frozenLeafNodes = facet.getRowLeafNodesByRange( + end - trailingRowCount + 1, + end, + )!; + + frozenLeafNodes.forEach((leafNode) => { + const newLeafNode = leafNode.clone(); + + newLeafNode.isFrozenTrailing = true; + extraNodes.push(newLeafNode); + }); + + const parents = getAllParents(frozenLeafNodes); + + parents.forEach((parent) => { + const newParent = parent.clone(); + + newParent.isFrozenTrailing = true; + + clipFrozenTrailingRowHeadNode( + newParent, + facet.frozenGroupAreas[FrozenGroupArea.TrailingRow], + ); + + extraNodes.push(newParent); + }); + } + + return extraNodes; +}; + +export const getExtraFrozenSeriesNodes = ( + facet: FrozenFacet, + nodes: Node[], +) => { + const extraNodes: Node[] = []; + + const { start, end } = facet.getCellRange(); + const { rowCount, trailingRowCount } = facet.getFrozenOptions(); + + const includeChildInRowIndexRange = (node: Node, range: [number, number]) => { + const rowIdx = node.rowIndex; + + if (isNumber(rowIdx) && rowIdx >= range[0] && rowIdx <= range[1]) { + return true; + } + + const children = node.children ?? []; + + for (let i = 0; i < children.length; i++) { + if (includeChildInRowIndexRange(children[i], range)) { + return true; + } + } + + return false; + }; + + if (rowCount) { + const range: [number, number] = [start, start + rowCount - 1]; + + nodes.forEach((node) => { + if ( + node.relatedNode && + !includeChildInRowIndexRange(node.relatedNode, range) + ) { + return; + } + + const newNode = node.clone(); + + newNode.isFrozenHead = true; + + clipFrozenRowHeadNode( + newNode, + facet.frozenGroupAreas[FrozenGroupArea.Row], + ); + + extraNodes.push(newNode); + }); + } + + if (trailingRowCount) { + const range: [number, number] = [end - trailingRowCount + 1, end]; + + nodes.forEach((node) => { + if ( + node.relatedNode && + !includeChildInRowIndexRange(node.relatedNode, range) + ) { + return; + } + + const newNode = node.clone(); + + newNode.isFrozenTrailing = true; + + clipFrozenTrailingRowHeadNode( + newNode, + facet.frozenGroupAreas[FrozenGroupArea.TrailingRow], + ); + + extraNodes.push(newNode); + }); + } + + return extraNodes; +}; + +export const getExtraFrozenColNodes = (facet: FrozenFacet) => { + const extraNodes: Node[] = []; + + const { colCount, trailingColCount } = facet.getFrozenOptions(); + + if (colCount) { + const { x, width } = facet.frozenGroupAreas[FrozenGroupArea.Col]; + + const frozenLeafNodes = facet.getColLeafNodesByRange(0, colCount - 1)!; + + frozenLeafNodes.forEach((leafNode) => { + const newLeafNode = leafNode.clone(); + + newLeafNode.isFrozenHead = true; + extraNodes.push(newLeafNode); + }); + + const parents = getAllParents(frozenLeafNodes); + + parents.forEach((parent) => { + const newParent = parent.clone(); + + newParent.isFrozenHead = true; + + if (newParent.x < x) { + newParent.width -= x - newParent.x; + newParent.x = x; + } + + if (newParent.x + newParent.width > x + width) { + newParent.width -= newParent.x + newParent.width - x - width; + } + + extraNodes.push(newParent); + }); + } + + if (trailingColCount) { + const { x, width } = facet.frozenGroupAreas[FrozenGroupArea.TrailingCol]; + + const total = facet.getColLeafNodes().length; + const frozenLeafNodes = facet.getColLeafNodesByRange( + total - trailingColCount, + total - 1, + )!; + + frozenLeafNodes.forEach((leafNode) => { + const newLeafNode = leafNode.clone(); + + newLeafNode.isFrozenTrailing = true; + extraNodes.push(newLeafNode); + }); + + const parents = getAllParents(frozenLeafNodes); + + parents.forEach((parent) => { + const newParent = parent.clone(); + + newParent.isFrozenTrailing = true; + + if (newParent.x + newParent.width > x + width) { + newParent.width -= newParent.x + newParent.width - x - width; + } + + if (newParent.x < x) { + newParent.width -= x - newParent.x; + newParent.x = x; + } + + extraNodes.push(newParent); + }); + } + + return extraNodes; +}; + +export const getFrozenColOffset = ( + facet: FrozenFacet, + cornerWidth: number = 0, + scrollX: number = 0, +) => { + const isFrozenRowHeader = facet.spreadsheet.isFrozenRowHeader(); + + if (isFrozenRowHeader) { + return 0; + } + + return scrollX <= cornerWidth ? scrollX : cornerWidth; +}; + +export const getFrozenTrailingColOffset = ( + facet: FrozenFacet, + viewportWidth: number, +) => { + const trailingCol = facet.frozenGroupAreas[FrozenGroupArea.TrailingCol]; + const trailingColWidth = trailingCol.x + trailingCol.width; + const trailingColOffset = + viewportWidth > trailingColWidth ? 0 : trailingColWidth - viewportWidth; + + return trailingColOffset; +}; + +export const getFrozenTrailingRowOffset = ( + facet: FrozenFacet, + viewportHeight: number, + paginationScrollY: number, +) => { + const trailingRow = facet.frozenGroupAreas[FrozenGroupArea.TrailingRow]; + const trailingRowHeight = + trailingRow.y + trailingRow.height - paginationScrollY; + const trailingRowOffset = + viewportHeight > trailingRowHeight + ? paginationScrollY + : paginationScrollY + trailingRowHeight - viewportHeight; + + return trailingRowOffset; +}; + +export const getScrollGroupClip = (facet: FrozenFacet, position: PointLike) => { + const isFrozenRowHeader = facet.spreadsheet.isFrozenRowHeader(); + + const frozenGroupAreas = facet.frozenGroupAreas; + + const frozenColGroupWidth = frozenGroupAreas[FrozenGroupArea.Col].width; + const frozenTrailingColGroupWidth = + frozenGroupAreas[FrozenGroupArea.TrailingCol].width; + + let x; + + if (isFrozenRowHeader) { + x = position.x + frozenColGroupWidth; + } else if (frozenColGroupWidth) { + x = Frame.getVerticalBorderWidth(facet.spreadsheet) + frozenColGroupWidth; + } else { + x = 0; + } + + const viewportWidth = facet.panelBBox.viewportWidth; + + return { + x, + width: + (isFrozenRowHeader ? viewportWidth : position.x + viewportWidth) - + frozenColGroupWidth - + frozenTrailingColGroupWidth, + }; +}; diff --git a/packages/s2-core/src/facet/layout/node.ts b/packages/s2-core/src/facet/layout/node.ts index 68758f8563..6e396bb895 100644 --- a/packages/s2-core/src/facet/layout/node.ts +++ b/packages/s2-core/src/facet/layout/node.ts @@ -115,6 +115,11 @@ export class Node { // series number width public seriesNumberWidth: number; + /** + * 给序号列单元格用,标识该序号单元格对应了行头节点,有了关联关系后,就可以在行头冻结时做区分 + */ + public relatedNode: Node; + // spreadsheet instance public spreadsheet: SpreadSheet; @@ -133,7 +138,11 @@ export class Node { public isTotalRoot?: boolean; - public isFrozen?: boolean; + /* 是否是冻结开头的 node , 比如 frozenRow frozenCol */ + public isFrozenHead?: boolean; + + /* 是否是冻结结尾的 node , 比如 frozenTrailingRow frozenTrailingCol */ + public isFrozenTrailing?: boolean; public shallowRender?: boolean; @@ -356,4 +365,12 @@ export class Node { public isSeriesNumberNode() { return this.field === SERIES_NUMBER_FIELD; } + + public clone() { + return Object.create(this) as Node; + } + + public get isFrozen() { + return this.isFrozenHead || this.isFrozenTrailing; + } } diff --git a/packages/s2-core/src/facet/pivot-facet.ts b/packages/s2-core/src/facet/pivot-facet.ts index 8d190614ab..6454d96f8c 100644 --- a/packages/s2-core/src/facet/pivot-facet.ts +++ b/packages/s2-core/src/facet/pivot-facet.ts @@ -1,4 +1,3 @@ -import { Group, Rect, type LineStyleProps } from '@antv/g'; import { filter, forEach, @@ -19,11 +18,7 @@ import { import { ColCell, RowCell, SeriesNumberCell } from '../cell'; import { DEFAULT_TREE_ROW_CELL_WIDTH, - FRONT_GROUND_GROUP_FROZEN_Z_INDEX, - FrozenGroupType, - KEY_GROUP_FROZEN_SPLIT_LINE, LAYOUT_SAMPLE_COUNT, - SPLIT_LINE_WIDTH, type IconTheme, type MultiData, type ViewMeta, @@ -37,21 +32,20 @@ import type { SimpleData, } from '../common/interface'; import type { PivotDataSet } from '../data-set/pivot-data-set'; -import { renderLine, safeJsonParse } from '../utils'; +import { getValidFrozenOptionsForPivot, safeJsonParse } from '../utils'; import { getDataCellId } 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 { getRowsForGrid } from '../utils/grid'; import { floor } from '../utils/math'; import { getCellWidth } from '../utils/text'; +import { getAllChildCells } from '../utils/get-all-child-cells'; import { FrozenFacet } from './frozen-facet'; import { Frame } from './header'; import { buildHeaderHierarchy } from './layout/build-header-hierarchy'; import type { Hierarchy } from './layout/hierarchy'; import { layoutCoordinate } from './layout/layout-hooks'; import { Node } from './layout/node'; -import { getFrozenRowCfgPivot } from './utils'; export class PivotFacet extends FrozenFacet { get rowCellTheme() { @@ -206,6 +200,8 @@ export class PivotFacet extends FrozenFacet { this.adjustColTotalNodesCoordinate(colsHierarchy); } + protected calculateRowOffsets(): void {} + private adjustColTotalNodesCoordinate(colsHierarchy: Hierarchy) { if (!isEmpty(this.spreadsheet.options.totals?.col)) { this.adjustTotalNodesCoordinate({ @@ -1002,101 +998,20 @@ export class PivotFacet extends FrozenFacet { * @description 对于透视表, 序号属于 RowCell */ public getSeriesNumberCells(): SeriesNumberCell[] { - return filter( - this.getSeriesNumberHeader()?.children, - (element: SeriesNumberCell) => element instanceof SeriesNumberCell, - ) as unknown[] as SeriesNumberCell[]; - } - - protected updateFrozenGroupGrid(): void { - [FrozenGroupType.FROZEN_ROW].forEach((key) => { - if (!this.frozenGroupInfo[key].range) { - return; - } - - let cols: number[] = []; - let rows: number[] = []; - - if (key.toLowerCase().includes('row')) { - const [rowMin, rowMax] = this.frozenGroupInfo[key].range || []; - - cols = this.gridInfo.cols; - rows = getRowsForGrid(rowMin, rowMax, this.viewCellHeights); - } - - this[`${key}Group`].updateGrid( - { - cols, - rows, - }, - `${key}Group`, - ); - }); - } - - protected getFrozenOptions() { - return getFrozenRowCfgPivot( - this.spreadsheet.options, - this.layoutResult.rowNodes, - ); - } + const headerChildren = (this.getSeriesNumberHeader()?.children || + []) as SeriesNumberCell[]; - public enableFrozenFirstRow(): boolean { - return !!this.getFrozenOptions().rowCount; + return getAllChildCells(headerChildren, SeriesNumberCell); } - protected renderFrozenGroupSplitLine = (scrollX: number, scrollY: number) => { - this.foregroundGroup.getElementById(KEY_GROUP_FROZEN_SPLIT_LINE)?.remove(); - if (this.enableFrozenFirstRow()) { - // 在分页条件下需要额外处理 Y 轴滚动值 - const relativeScrollY = floor(scrollY - this.getPaginationScrollY()); - const splitLineGroup = this.foregroundGroup.appendChild( - new Group({ - id: KEY_GROUP_FROZEN_SPLIT_LINE, - style: { - zIndex: FRONT_GROUND_GROUP_FROZEN_Z_INDEX, - }, - }), + public getFrozenOptions() { + if (!this.validFrozenOptions) { + this.validFrozenOptions = getValidFrozenOptionsForPivot( + super.getFrozenOptions(), + this.spreadsheet.options, ); - - const { splitLine } = this.spreadsheet.theme; - - const horizontalBorderStyle: Partial = { - lineWidth: SPLIT_LINE_WIDTH, - stroke: splitLine?.horizontalBorderColor, - opacity: splitLine?.horizontalBorderColorOpacity, - }; - - const cellRange = this.getCellRange(); - const y = - this.panelBBox.y + - this.getTotalHeightForRange(cellRange.start, cellRange.start); - const width = - this.cornerBBox.width + - Frame.getVerticalBorderWidth(this.spreadsheet) + - this.panelBBox.viewportWidth; - - renderLine(splitLineGroup, { - ...horizontalBorderStyle, - x1: 0, - x2: width, - y1: y, - y2: y, - }); - - if (splitLine!.showShadow && relativeScrollY > 0) { - splitLineGroup.appendChild( - new Rect({ - style: { - x: 0, - y, - width, - height: splitLine?.shadowWidth!, - fill: this.getShadowFill(90), - }, - }), - ); - } } - }; + + return this.validFrozenOptions; + } } diff --git a/packages/s2-core/src/facet/table-facet.ts b/packages/s2-core/src/facet/table-facet.ts index 8743f6b800..f286c2a0b4 100644 --- a/packages/s2-core/src/facet/table-facet.ts +++ b/packages/s2-core/src/facet/table-facet.ts @@ -14,7 +14,6 @@ import { i18n } from '../common'; import { EMPTY_PLACEHOLDER_GROUP_CONTAINER_Z_INDEX, KEY_GROUP_EMPTY_PLACEHOLDER, - KEY_GROUP_FROZEN_ROW_RESIZE_AREA, KEY_GROUP_ROW_RESIZE_AREA, LayoutWidthType, S2Event, @@ -40,7 +39,6 @@ import { getDataCellId } from '../utils/cell/data-cell'; import { getOccupiedWidthForTableCol } from '../utils/cell/table-col-cell'; 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 type { BaseFacet } from './base-facet'; import { CornerBBox } from './bbox/corner-bbox'; @@ -51,7 +49,6 @@ import { buildHeaderHierarchy } from './layout/build-header-hierarchy'; import { Hierarchy } from './layout/hierarchy'; import { layoutCoordinate } from './layout/layout-hooks'; import { Node } from './layout/node'; -import { getFrozenLeafNodesCount, isFrozenTrailingRow } from './utils'; export class TableFacet extends FrozenFacet { public emptyPlaceholderGroup: Group; @@ -84,11 +81,6 @@ export class TableFacet extends FrozenFacet { this.initEmptyPlaceholderGroup(); } - public init() { - super.init(); - this.initRowOffsets(); - } - public render() { super.render(); this.renderEmptyPlaceholder(); @@ -221,7 +213,7 @@ export class TableFacet extends FrozenFacet { ); } - protected initRowOffsets() { + protected calculateRowOffsets() { const { style } = this.spreadsheet.options; const heightByField = style?.rowCell?.heightByField; @@ -374,24 +366,11 @@ export class TableFacet extends FrozenFacet { return null; } - const cellHeight = this.getCellHeightByRowIndex(rowIndex); - const cellRange = this.getCellRange(); - const { trailingRowCount = 0 } = getValidFrozenOptions( - this.spreadsheet.options.frozen!, - colLeafNodes.length, - cellRange.end - cellRange.start + 1, - ); - let data: ViewMetaData | SimpleData | undefined; const x = colNode.x; - let y = this.viewCellHeights.getCellOffsetY(rowIndex); - - if (isFrozenTrailingRow(rowIndex, cellRange.end, trailingRowCount)) { - y = - this.panelBBox.height - - this.getTotalHeightForRange(rowIndex, cellRange.end); - } + const y = this.viewCellHeights.getCellOffsetY(rowIndex); + const cellHeight = this.getCellHeightByRowIndex(rowIndex); if (options.seriesNumber?.enable && colNode.field === SERIES_NUMBER_FIELD) { data = rowIndex + 1; @@ -505,7 +484,6 @@ export class TableFacet extends FrozenFacet { this.updateColsHierarchySampleMaxHeightNodes(colsHierarchy); this.calculateColNodesHeight(colsHierarchy); this.calculateColNodeWidthAndX(colLeafNodes); - this.calculateFrozenColNodeX(colsHierarchy); this.updateCustomFieldsSampleNodes(colsHierarchy); this.adjustCustomColLeafNodesHeight({ leafNodes: colLeafNodes, @@ -513,39 +491,6 @@ export class TableFacet extends FrozenFacet { }); } - private calculateFrozenColNodeX(colsHierarchy: Hierarchy) { - const topLevelNodes = colsHierarchy.getNodes(0); - const { trailingColCount = 0 } = getValidFrozenOptions( - this.spreadsheet.options.frozen!, - topLevelNodes.length, - ); - - const colNodes = colsHierarchy.getNodes(); - let preLeafNode = Node.blankNode(); - - const width = - this.getCanvasSize().width - - Frame.getVerticalBorderWidth(this.spreadsheet); - - if (trailingColCount > 0) { - const { trailingColCount: realFrozenTrailingColCount } = - getFrozenLeafNodesCount(topLevelNodes, 0, trailingColCount); - const leafNodes = colNodes.filter((node) => node.isLeaf); - - for (let i = 1; i <= realFrozenTrailingColCount; i++) { - const currentNode = leafNodes[leafNodes.length - i]; - - if (i === 1) { - currentNode.x = width - currentNode.width; - } else { - currentNode.x = preLeafNode.x - currentNode.width; - } - - preLeafNode = currentNode; - } - } - } - /** * Auto column no-leaf node's width and x coordinate * @param colLeafNodes @@ -702,7 +647,7 @@ export class TableFacet extends FrozenFacet { }; } - protected updateRowResizeArea() { + protected renderRowResizeArea() { const { resize } = this.spreadsheet.options.interaction!; const shouldDrawResize = isBoolean(resize) @@ -716,18 +661,11 @@ export class TableFacet extends FrozenFacet { const rowResizeGroup = this.foregroundGroup.getElementById( KEY_GROUP_ROW_RESIZE_AREA, ); - const rowResizeFrozenGroup = this.foregroundGroup.getElementById( - KEY_GROUP_FROZEN_ROW_RESIZE_AREA, - ); if (rowResizeGroup) { rowResizeGroup.removeChildren(); } - if (rowResizeFrozenGroup) { - rowResizeFrozenGroup.removeChildren(); - } - const cells = getAllChildCells( this.panelGroup.children as TableDataCell[], TableDataCell, diff --git a/packages/s2-core/src/facet/utils.ts b/packages/s2-core/src/facet/utils.ts index 172cd4c4b0..1477cf2369 100644 --- a/packages/s2-core/src/facet/utils.ts +++ b/packages/s2-core/src/facet/utils.ts @@ -1,15 +1,13 @@ import type { Group } from '@antv/g'; import { findIndex, isEmpty, isNil } from 'lodash'; import type { FrozenCellIndex } from '../common/constant/frozen'; -import { FrozenCellType } from '../common/constant/frozen'; +import { FrozenGroupType } from '../common/constant/frozen'; import { DEFAULT_PAGE_INDEX } from '../common/constant/pagination'; import type { CustomHeaderFields, Fields, Pagination, - S2Options, - S2PivotSheetFrozenOptions, - S2TableSheetFrozenOptions, + S2BaseFrozenOptions, ScrollSpeedRatio, } from '../common/interface'; import type { Indexes } from '../utils/indexes'; @@ -151,12 +149,12 @@ export const translateGroupY = (group: Group, scrollY: number) => { * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * @description returns which group data cell belongs in frozen mode */ -export const getFrozenDataCellType = ( +export const getFrozenGroupTypeByCell = ( meta: { colIndex: number; rowIndex: number; }, - frozenOptions: S2TableSheetFrozenOptions, + frozenOptions: S2BaseFrozenOptions, colLength: number, cellRange: { start: number; @@ -172,29 +170,29 @@ export const getFrozenDataCellType = ( const { colIndex, rowIndex } = meta; if (isFrozenRow(rowIndex, cellRange.start, rowCount)) { - return FrozenCellType.ROW; + return FrozenGroupType.Row; } if (isFrozenTrailingRow(rowIndex, cellRange.end, trailingRowCount)) { - return FrozenCellType.TRAILING_ROW; + return FrozenGroupType.TrailingRow; } if (isFrozenCol(colIndex, colCount)) { - return FrozenCellType.COL; + return FrozenGroupType.Col; } if (isFrozenTrailingCol(colIndex, trailingColCount, colLength)) { - return FrozenCellType.TRAILING_COL; + return FrozenGroupType.TrailingCol; } - return FrozenCellType.SCROLL; + return FrozenGroupType.Scroll; }; /** * @description calculate all cells in frozen group's intersection region */ export const calculateFrozenCornerCells = ( - frozenOptions: S2TableSheetFrozenOptions, + frozenOptions: S2BaseFrozenOptions, colLength: number, cellRange: { start: number; @@ -208,17 +206,17 @@ export const calculateFrozenCornerCells = ( trailingRowCount: frozenTrailingRowCount = 0, } = frozenOptions; - const result: { - [key: string]: FrozenCellIndex[]; - } = { - [FrozenCellType.TOP]: [], - [FrozenCellType.BOTTOM]: [], + const result = { + [FrozenGroupType.TopLeft]: [] as FrozenCellIndex[], + [FrozenGroupType.TopRight]: [] as FrozenCellIndex[], + [FrozenGroupType.BottomLeft]: [] as FrozenCellIndex[], + [FrozenGroupType.BottomRight]: [] as FrozenCellIndex[], }; - // frozenColGroup with frozenRowGroup or frozenTrailingRowGroup. Top left and bottom left corner. + // frozenColGroup with frozenRowGroup or frozenTrailingRowGroup. Top left and Bottom left corner. for (let i = 0; i < frozenColCount; i++) { for (let j = cellRange.start; j < cellRange.start + frozenRowCount; j++) { - result[FrozenCellType.TOP].push({ + result[FrozenGroupType.TopLeft].push({ x: i, y: j, }); @@ -228,7 +226,7 @@ export const calculateFrozenCornerCells = ( for (let j = 0; j < frozenTrailingRowCount; j++) { const index = cellRange.end - j; - result[FrozenCellType.BOTTOM].push({ + result[FrozenGroupType.BottomLeft].push({ x: i, y: index, }); @@ -241,7 +239,7 @@ export const calculateFrozenCornerCells = ( const colIndex = colLength - 1 - i; for (let j = cellRange.start; j < cellRange.start + frozenRowCount; j++) { - result[FrozenCellType.TOP].push({ + result[FrozenGroupType.TopRight].push({ x: colIndex, y: j, }); @@ -251,7 +249,7 @@ export const calculateFrozenCornerCells = ( for (let j = 0; j < frozenTrailingRowCount; j++) { const index = cellRange.end - j; - result[FrozenCellType.BOTTOM].push({ + result[FrozenGroupType.BottomRight].push({ x: colIndex, y: index, }); @@ -267,7 +265,7 @@ export const calculateFrozenCornerCells = ( */ export const splitInViewIndexesWithFrozen = ( indexes: Indexes, - frozenOptions: S2TableSheetFrozenOptions, + frozenOptions: S2BaseFrozenOptions, colLength: number, cellRange: { start: number; @@ -351,55 +349,6 @@ export const getCellRange = ( }; }; -/** - * 给定一个一层的 node 数组以及左右固定列的数量,计算出实际固定列(叶子节点)的数量 - * @param nodes - * @param colCount - * @param trailingColCount - * @returns {colCount, trailingColCount} - */ -export const getFrozenLeafNodesCount = ( - nodes: Node[], - colCount: number, - trailingColCount: number, -): { colCount: number; trailingColCount: number } => { - const getLeafNodesCount = (node: Node) => { - if (node.isLeaf) { - return 1; - } - - if (node.children) { - return node.children.reduce((pCount, item) => { - pCount += getLeafNodesCount(item); - - return pCount; - }, 0); - } - - return 0; - }; - - if (colCount) { - colCount = nodes.slice(0, colCount).reduce((count, node) => { - count += getLeafNodesCount(node); - - return count; - }, 0); - } - - if (trailingColCount) { - trailingColCount = nodes - .slice(nodes.length - trailingColCount) - .reduce((count, node) => { - count += getLeafNodesCount(node); - - return count; - }, 0); - } - - return { colCount, trailingColCount }; -}; - /** * 明细表多级表头根据一个 node 返回其所属顶层节点 * @param node @@ -480,45 +429,3 @@ export const areAllFieldsEmpty = (fields: Fields) => { isEmpty(fields.rows) && isEmpty(fields.columns) && isEmpty(fields.values) ); }; - -/** - * get frozen options pivot-sheet (business limit) - * @param options - * @returns - */ -export const getFrozenRowCfgPivot = ( - options: S2Options, - rowNodes: Node[], -): S2PivotSheetFrozenOptions & - S2TableSheetFrozenOptions & { - rowHeight: number; - } => { - /** - * series number cell 可以自定义布局,和 row cell 不一定是 1 对 1 的关系 - * seriesNumber 暂时禁用 首行冻结 - * */ - const { pagination, frozen, hierarchyType, seriesNumber } = options; - - const enablePagination = pagination && pagination.pageSize; - let firstRow = false; - const headNode = rowNodes?.[0]; - - if (!enablePagination && !seriesNumber?.enable && frozen?.firstRow) { - const treeMode = hierarchyType === 'tree'; - - // tree mode - // first node no children: entire row - firstRow = treeMode || headNode?.children?.length === 0; - } - - const effectiveFrozenFirstRow = firstRow && !!headNode; - - return { - rowCount: effectiveFrozenFirstRow ? 1 : 0, - colCount: 0, - trailingColCount: 0, - trailingRowCount: 0, - firstRow, - rowHeight: effectiveFrozenFirstRow ? headNode.height : 0, - }; -}; diff --git a/packages/s2-core/src/interaction/brush-selection/base-brush-selection.ts b/packages/s2-core/src/interaction/brush-selection/base-brush-selection.ts index 5fdda45839..dea859f7fa 100644 --- a/packages/s2-core/src/interaction/brush-selection/base-brush-selection.ts +++ b/packages/s2-core/src/interaction/brush-selection/base-brush-selection.ts @@ -8,6 +8,7 @@ import { cloneDeep, isEmpty, isNil, map, throttle } from 'lodash'; import { ColCell, DataCell, RowCell } from '../../cell'; import { FRONT_GROUND_GROUP_BRUSH_SELECTION_Z_INDEX, + FrozenGroupArea, InteractionStateName, InterceptType, S2Event, @@ -28,7 +29,7 @@ import type { ViewMeta, } from '../../common/interface'; import type { BBox } from '../../engine/interface'; -import type { TableFacet } from '../../facet'; +import type { FrozenFacet, TableFacet } from '../../facet'; import type { Node } from '../../facet/layout/node'; import { isFrozenCol, @@ -42,7 +43,6 @@ import { getScrollOffsetForCol, getScrollOffsetForRow, } from '../../utils/interaction'; -import { getValidFrozenOptions } from '../../utils/layout/frozen'; import type { BaseEventImplement } from '../base-event'; import { BaseEvent } from '../base-interaction'; @@ -206,9 +206,9 @@ export class BaseBrushSelection public validateYIndex = (yIndex: number) => { const { facet } = this.spreadsheet; - const frozenInfo = (facet as unknown as TableFacet).frozenGroupInfo; + const frozenGroupAreas = (facet as unknown as TableFacet).frozenGroupAreas; let min = 0; - const frozenRowRange = frozenInfo?.frozenRow?.range; + const frozenRowRange = frozenGroupAreas?.frozenRow?.range; if (frozenRowRange?.[1]) { min = frozenRowRange[1] + 1; @@ -219,7 +219,7 @@ export class BaseBrushSelection } let max = facet.getCellRange().end; - const frozenTrailingRowRange = frozenInfo?.frozenTrailingRow?.range; + const frozenTrailingRowRange = frozenGroupAreas?.frozenTrailingRow?.range; if (frozenTrailingRowRange?.[0]) { max = frozenTrailingRowRange[0] - 1; @@ -234,10 +234,10 @@ export class BaseBrushSelection public validateXIndex = (xIndex: number) => { const { facet } = this.spreadsheet; - const frozenInfo = (facet as unknown as TableFacet).frozenGroupInfo; + const frozenGroupAreas = (facet as TableFacet).frozenGroupAreas; let min = 0; - const frozenColRange = frozenInfo?.frozenCol?.range; + const frozenColRange = frozenGroupAreas[FrozenGroupArea.Col].range; if (frozenColRange?.[1]) { min = frozenColRange[1] + 1; @@ -248,7 +248,8 @@ export class BaseBrushSelection } let max = facet.getColLeafNodes().length - 1; - const frozenTrailingColRange = frozenInfo?.frozenTrailingCol?.range; + const frozenTrailingColRange = + frozenGroupAreas[FrozenGroupArea.TrailingCol].range; if (frozenTrailingColRange?.[0]) { max = frozenTrailingColRange[0] - 1; @@ -265,31 +266,29 @@ export class BaseBrushSelection colIndex: number, dir: ScrollDirection, ) => { - const { facet, dataSet, options } = this.spreadsheet; - const dataLength = dataSet.getDisplayDataSet().length; + const { facet } = this.spreadsheet; const colLength = facet.getColLeafNodes().length; - const { - trailingColCount: frozenTrailingColCount, - colCount: frozenColCount, - } = getValidFrozenOptions(options.frozen!, colLength, dataLength); + const { colCount, trailingColCount } = ( + facet as FrozenFacet + ).getFrozenOptions(); const panelIndexes = (facet as unknown as TableFacet) .panelScrollGroupIndexes; if ( - frozenTrailingColCount! > 0 && - dir === ScrollDirection.SCROLL_DOWN && - isFrozenTrailingCol(colIndex, frozenTrailingColCount!, colLength) + colCount > 0 && + dir === ScrollDirection.SCROLL_UP && + isFrozenCol(colIndex, colCount) ) { - return panelIndexes[1]; + return panelIndexes[0]; } if ( - frozenColCount! > 0 && - dir === ScrollDirection.SCROLL_UP && - isFrozenCol(colIndex, frozenColCount!) + trailingColCount > 0 && + dir === ScrollDirection.SCROLL_DOWN && + isFrozenTrailingCol(colIndex, trailingColCount, colLength) ) { - return panelIndexes[0]; + return panelIndexes[1]; } return colIndex; @@ -299,31 +298,28 @@ export class BaseBrushSelection rowIndex: number, dir: ScrollDirection, ) => { - const { facet, dataSet, options } = this.spreadsheet; - const dataLength = dataSet.getDisplayDataSet().length; - const colLength = facet.getColLeafNodes().length; + const { facet } = this.spreadsheet; const cellRange = facet.getCellRange(); - const { - trailingRowCount: frozenTrailingRowCount, - rowCount: frozenRowCount, - } = getValidFrozenOptions(options.frozen!, colLength, dataLength); + const { rowCount, trailingRowCount } = ( + facet as FrozenFacet + ).getFrozenOptions(); const panelIndexes = (facet as unknown as TableFacet) .panelScrollGroupIndexes; if ( - frozenTrailingRowCount! > 0 && - dir === ScrollDirection.SCROLL_DOWN && - isFrozenTrailingRow(rowIndex, cellRange.end, frozenTrailingRowCount!) + rowCount > 0 && + dir === ScrollDirection.SCROLL_UP && + isFrozenRow(rowIndex, cellRange.start, rowCount) ) { - return panelIndexes[3]; + return panelIndexes[2]; } if ( - frozenRowCount! > 0 && - dir === ScrollDirection.SCROLL_UP && - isFrozenRow(rowIndex, cellRange.start, frozenRowCount!) + trailingRowCount > 0 && + dir === ScrollDirection.SCROLL_DOWN && + isFrozenTrailingRow(rowIndex, cellRange.end, trailingRowCount) ) { - return panelIndexes[2]; + return panelIndexes[3]; } return rowIndex; diff --git a/packages/s2-core/src/interaction/row-column-resize.ts b/packages/s2-core/src/interaction/row-column-resize.ts index 3b21cb8798..b33a396795 100644 --- a/packages/s2-core/src/interaction/row-column-resize.ts +++ b/packages/s2-core/src/interaction/row-column-resize.ts @@ -187,6 +187,8 @@ export class RowColumnResize extends BaseEvent implements BaseEventImplement { private bindMouseDown() { this.spreadsheet.on(S2Event.LAYOUT_RESIZE_MOUSE_DOWN, (event) => { + event?.preventDefault?.(); + const shape = event.target as Group; const resizeInfo = this.getCellAppendInfo(event.target); diff --git a/packages/s2-core/src/interaction/selected-cell-move.ts b/packages/s2-core/src/interaction/selected-cell-move.ts index 6f9d956406..d24b4f20cf 100644 --- a/packages/s2-core/src/interaction/selected-cell-move.ts +++ b/packages/s2-core/src/interaction/selected-cell-move.ts @@ -1,12 +1,15 @@ import type { FederatedPointerEvent as Event } from '@antv/g'; -import { get } from 'lodash'; -import { type CellMeta, CellType, type ViewMeta } from '../common'; +import { + type CellMeta, + CellType, + type ViewMeta, + FrozenGroupArea, +} from '../common'; import { InteractionKeyboardKey, S2Event } from '../common/constant'; -import { calculateInViewIndexes } from '../facet/utils'; import type { SpreadSheet } from '../sheet-type'; import { getDataCellId } from '../utils'; import { getRangeIndex, selectCells } from '../utils/interaction/select-event'; -import { floor } from '../utils/math'; +import type { FrozenFacet } from '../facet'; import { BaseEvent, type BaseEventImplement } from './base-interaction'; const SelectedCellMoveMap = [ @@ -174,28 +177,20 @@ export class SelectedCellMove extends BaseEvent implements BaseEventImplement { private getMoveInfo(code: string, cell: CellMeta | null, isJump: boolean) { const { spreadsheet } = this; - const { - rowCount: frozenRowCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - colCount: frozenColCount = 0, - trailingColCount: frozenTrailingColCount = 0, - } = spreadsheet.options.frozen!; - const rowLeafNodes = spreadsheet.facet.getRowLeafNodes(); + const { rowCount, trailingRowCount, colCount, trailingColCount } = ( + spreadsheet.facet as FrozenFacet + ).getFrozenOptions(); + + const { start, end } = spreadsheet.facet.getCellRange(); + const colLeafNodes = spreadsheet.facet.getColLeafNodes(); const [minCol, maxCol] = [ - 0 + frozenColCount, - colLeafNodes.length - frozenTrailingColCount - 1, - ]; - const [minRow, maxRow] = [ - 0 + frozenRowCount, - (spreadsheet.isTableMode() - ? spreadsheet.dataSet.getDisplayDataSet().length - : rowLeafNodes.length) - - frozenTrailingRowCount - - 1, + 0 + colCount, + colLeafNodes.length - trailingColCount - 1, ]; + const [minRow, maxRow] = [start + rowCount, end - trailingRowCount]; if (!cell) { return; @@ -249,69 +244,33 @@ export class SelectedCellMove extends BaseEvent implements BaseEventImplement { rowIndex: number, colIndex: number, ) { - const { - rowCount: frozenRowCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - } = spreadsheet.options.frozen!; - const facet = spreadsheet.facet; - const { - frozenColGroup, - frozenTrailingColGroup, - frozenRowGroup, - frozenTrailingRowGroup, - } = facet; + const facet = spreadsheet.facet as FrozenFacet; + const { rowCount } = facet.getFrozenOptions(); const colLeafNodes = facet.getColLeafNodes(); - const { scrollX, scrollY } = facet.getScrollOffset(); const { viewportHeight: height, viewportWidth: width } = facet.panelBBox; - const splitLineStyle = get(spreadsheet, 'theme.splitLine'); - const frozenColWidth = frozenColGroup - ? floor( - frozenColGroup.getBBox().width - - splitLineStyle.verticalBorderWidth / 2, - ) - : 0; - const frozenTrailingColWidth = frozenTrailingColGroup - ? floor(frozenTrailingColGroup.getBBox().width) - : 0; - const frozenRowHeight = frozenRowGroup - ? floor( - frozenRowGroup.getBBox().height - - splitLineStyle.horizontalBorderWidth / 2, - ) - : 0; - const frozenTrailingRowHeight = frozenTrailingRowGroup - ? floor(frozenTrailingRowGroup.getBBox().height) - : 0; - - const indexes = calculateInViewIndexes({ - scrollX, - scrollY, - widths: facet.viewCellWidths, - heights: facet.viewCellHeights, - viewport: { - width: width - frozenColWidth - frozenTrailingColWidth, - height: height - frozenRowHeight - frozenTrailingRowHeight, - x: frozenColWidth, - y: frozenRowHeight, - }, - rowRemainWidth: facet.getRealScrollX(facet.cornerBBox.width), - }); - // 小于0的初始值 - let offsetX = -1; - let offsetY = -1; + const frozenGroupAreas = facet.frozenGroupAreas; + const frozenColWidth = frozenGroupAreas[FrozenGroupArea.Col].width; + + const frozenTrailingColWidth = + frozenGroupAreas[FrozenGroupArea.TrailingCol].width; + + const frozenTrailingRowHeight = + frozenGroupAreas[FrozenGroupArea.TrailingRow].height; + + const indexes = facet.panelScrollGroupIndexes; const targetNode = colLeafNodes.find((node) => node.colIndex === colIndex); + let offsetX: number | null = null; + let offsetY: number | null = null; + // offsetX if (colIndex <= indexes[0]) { // scroll left offsetX = targetNode?.x! - frozenColWidth; - } else if ( - colIndex >= indexes[1] && - colIndex < colLeafNodes.length - frozenTrailingRowCount - ) { + } else if (colIndex >= indexes[1]) { // scroll right offsetX = targetNode?.x! + targetNode?.width! - width + frozenTrailingColWidth; @@ -320,12 +279,12 @@ export class SelectedCellMove extends BaseEvent implements BaseEventImplement { // offsetY if (rowIndex <= indexes[2]) { // scroll top - offsetY = facet.viewCellHeights.getCellOffsetY(rowIndex - frozenRowCount); + offsetY = facet.viewCellHeights.getCellOffsetY(rowIndex - rowCount); } else if (rowIndex >= indexes[3]) { // scroll bottom const y = facet.viewCellHeights.getCellOffsetY(rowIndex + 1); - offsetY = y + frozenTrailingRowHeight - height; + offsetY = y - height + frozenTrailingRowHeight; } return { offsetX, offsetY }; @@ -345,8 +304,8 @@ export class SelectedCellMove extends BaseEvent implements BaseEventImplement { const { scrollX, scrollY } = spreadsheet.facet.getScrollOffset(); facet.scrollWithAnimation({ - offsetX: { value: offsetX > -1 ? offsetX : scrollX }, - offsetY: { value: offsetY > -1 ? offsetY : scrollY }, + offsetX: { value: offsetX ?? scrollX }, + offsetY: { value: offsetY ?? scrollY }, }); } } diff --git a/packages/s2-core/src/sheet-type/pivot-sheet.ts b/packages/s2-core/src/sheet-type/pivot-sheet.ts index 723111bc91..024766bf80 100644 --- a/packages/s2-core/src/sheet-type/pivot-sheet.ts +++ b/packages/s2-core/src/sheet-type/pivot-sheet.ts @@ -33,10 +33,6 @@ export class PivotSheet extends SpreadSheet { return new PivotDataSet(this); } - public enableFrozenHeaders(): boolean { - return false; - } - public getContentHeight() { return this.facet.getContentHeight(); } diff --git a/packages/s2-core/src/sheet-type/spread-sheet.ts b/packages/s2-core/src/sheet-type/spread-sheet.ts index 32dfc4baf9..8e0ec242db 100644 --- a/packages/s2-core/src/sheet-type/spread-sheet.ts +++ b/packages/s2-core/src/sheet-type/spread-sheet.ts @@ -104,8 +104,6 @@ export abstract class SpreadSheet extends EE { public abstract getDataSet(): BaseDataSet; - public abstract enableFrozenHeaders(): boolean; - public abstract isPivotMode(): boolean; public abstract isCustomRowFields(): boolean; diff --git a/packages/s2-core/src/sheet-type/table-sheet.ts b/packages/s2-core/src/sheet-type/table-sheet.ts index 911602bde5..2bd1d9cca2 100644 --- a/packages/s2-core/src/sheet-type/table-sheet.ts +++ b/packages/s2-core/src/sheet-type/table-sheet.ts @@ -21,22 +21,6 @@ export class TableSheet extends SpreadSheet { return new TableDataSet(this); } - public enableFrozenHeaders(): boolean { - const { - rowCount: frozenRowCount = 0, - trailingRowCount: frozenTrailingRowCount = 0, - colCount: frozenColCount = 0, - trailingColCount: frozenTrailingColCount = 0, - } = this.options.frozen!; - - return ( - frozenRowCount > 0 || - frozenTrailingRowCount > 0 || - frozenColCount > 0 || - frozenTrailingColCount > 0 - ); - } - /** * Check if is pivot mode */ diff --git a/packages/s2-core/src/utils/condition/condition.ts b/packages/s2-core/src/utils/condition/condition.ts index 598b34f2ac..f1e54652ac 100644 --- a/packages/s2-core/src/utils/condition/condition.ts +++ b/packages/s2-core/src/utils/condition/condition.ts @@ -56,8 +56,8 @@ export const getIntervalScale = (minValue = 0, maxValue = 0) => { }; }; -export const findFieldCondition = ( - conditions: Condition[], +export const findFieldCondition = ( + conditions: Condition[], valueField: string, ) => { return findLast(conditions, (item) => { diff --git a/packages/s2-core/src/utils/indexes.ts b/packages/s2-core/src/utils/indexes.ts index 694d3fad6c..141200d4e2 100644 --- a/packages/s2-core/src/utils/indexes.ts +++ b/packages/s2-core/src/utils/indexes.ts @@ -1,14 +1,11 @@ import { isEmpty } from 'lodash'; +import type { FrozenGroupArea } from '../common'; export type Indexes = [number, number, number, number]; export type PanelIndexes = { center: Indexes; - frozenRow?: Indexes; - frozenCol?: Indexes; - frozenTrailingRow?: Indexes; - frozenTrailingCol?: Indexes; -}; +} & Partial>; export interface Diff { add: [number, number][]; diff --git a/packages/s2-core/src/utils/interaction/scroll.ts b/packages/s2-core/src/utils/interaction/scroll.ts index 5127f9ec61..b51f5c4344 100644 --- a/packages/s2-core/src/utils/interaction/scroll.ts +++ b/packages/s2-core/src/utils/interaction/scroll.ts @@ -2,6 +2,7 @@ import { isNil } from 'lodash'; import { ScrollDirection } from '../../common/constant/interaction'; import type { TableFacet } from '../../facet'; import type { SpreadSheet } from '../../sheet-type'; +import { FrozenGroupArea } from '../../common'; // 获取滚动指定列到视口内的滚动 x 轴 Offset。滚动到视口边缘位置,左侧和右侧视滚动方向而定。 export const getScrollOffsetForCol = ( @@ -10,10 +11,10 @@ export const getScrollOffsetForCol = ( spreadsheet: SpreadSheet, ) => { const { facet } = spreadsheet; - const { width } = facet.panelBBox; - const info = (facet as unknown as TableFacet)?.frozenGroupInfo; - const frozenColWidth = info?.frozenCol.width ?? 0; - const frozenTrailingColWidth = info?.frozenTrailingCol.width ?? 0; + const { viewportWidth } = facet.panelBBox; + const areas = (facet as TableFacet)?.frozenGroupAreas; + const frozenColWidth = areas[FrozenGroupArea.Col].width; + const frozenTrailingColWidth = areas[FrozenGroupArea.TrailingCol].width; const colNode = facet.getColLeafNodes()[colIndex]; @@ -21,7 +22,7 @@ export const getScrollOffsetForCol = ( return colNode.x - frozenColWidth; } - return colNode.x + colNode.width - (width - frozenTrailingColWidth); + return colNode.x + colNode.width - (viewportWidth - frozenTrailingColWidth); }; // 获取滚动指定行到视口内的滚动 y 轴 Offset。滚动到视口边缘位置,上侧和下侧视滚动方向而定。 @@ -32,20 +33,20 @@ export const getScrollOffsetForRow = ( ) => { const { facet } = spreadsheet; const { getCellOffsetY } = facet.viewCellHeights; - const { height } = facet.panelBBox; + const { viewportHeight } = facet.panelBBox; const rowOffset = getCellOffsetY(rowIndex + 1); if (isNil(rowOffset)) { return 0; } - const info = (facet as unknown as TableFacet)?.frozenGroupInfo; - const frozenRowHeight = info?.frozenRow.height ?? 0; - const frozenTrailingRowHeight = info?.frozenTrailingRow.height ?? 0; + const areas = (facet as TableFacet)?.frozenGroupAreas; + const frozenRowHeight = areas[FrozenGroupArea.Row].height; + const frozenTrailingRowHeight = areas[FrozenGroupArea.TrailingRow].height; if (direction === ScrollDirection.SCROLL_UP) { return getCellOffsetY(rowIndex) - frozenRowHeight; } - return rowOffset - (height - frozenTrailingRowHeight); + return rowOffset - (viewportHeight - frozenTrailingRowHeight); }; diff --git a/packages/s2-core/src/utils/layout/frozen.ts b/packages/s2-core/src/utils/layout/frozen.ts index e43919bf03..32163a0e33 100644 --- a/packages/s2-core/src/utils/layout/frozen.ts +++ b/packages/s2-core/src/utils/layout/frozen.ts @@ -1,16 +1,13 @@ -import type { S2TableSheetFrozenOptions } from '../../common/interface'; +import { DEFAULT_FROZEN_COUNTS } from '../../common'; +import type { S2Options, S2BaseFrozenOptions } from '../../common/interface'; export const getValidFrozenOptions = ( - defaultFrozenOptions: S2TableSheetFrozenOptions, + defaultFrozenOptions: S2BaseFrozenOptions = {}, colLength: number, dataLength = 0, -) => { - // 如果没有传行列冻结选项,提前返回 - if (!Object.values(defaultFrozenOptions).find((item) => item > 0)) { - return defaultFrozenOptions; - } - - const frozenOptions: S2TableSheetFrozenOptions = { +): Required => { + const frozenOptions: Required = { + ...DEFAULT_FROZEN_COUNTS, ...defaultFrozenOptions, }; @@ -36,3 +33,29 @@ export const getValidFrozenOptions = ( return frozenOptions; }; + +/** + * get frozen options pivot-sheet (business limit) + * @param options + * @returns + */ +export const getValidFrozenOptionsForPivot = ( + frozen: Required, + options: S2Options, +): Required => { + /** + * series number cell 可以自定义布局,和 row cell 不一定是 1 对 1 的关系 + * seriesNumber 暂时禁用 首行冻结 + * */ + const { seriesNumber, layoutSeriesNumberNodes } = options; + + if (seriesNumber?.enable && layoutSeriesNumberNodes) { + return { + ...frozen, + rowCount: 0, + trailingRowCount: 0, + }; + } + + return frozen; +}; diff --git a/packages/s2-react/playground/config.tsx b/packages/s2-react/playground/config.tsx index 8ed8970b4f..19bb24afd5 100644 --- a/packages/s2-react/playground/config.tsx +++ b/packages/s2-react/playground/config.tsx @@ -7,7 +7,7 @@ import { type CustomHeaderField, type CustomTreeNode, type S2DataConfig, - type S2TableSheetFrozenOptions, + type S2BaseFrozenOptions, type ThemeCfg, } from '@antv/s2'; import { getBaseSheetComponentOptions } from '@antv/s2-shared'; @@ -55,6 +55,14 @@ export const tableSheetMultipleColumns: CustomTreeNode[] = [ { field: 'number', title: '数量' }, ], }, + { + field: 'money1', + title: '金额1', + children: [ + { field: 'price1', title: '价格1', description: '价格描述' }, + { field: 'number1', title: '数量1' }, + ], + }, ]; export const tableSheetDataCfg: S2DataConfig = { @@ -245,11 +253,16 @@ export const s2ConditionsOptions: SheetComponentOptions['conditions'] = { ], }; -export const TableSheetFrozenOptions: S2TableSheetFrozenOptions = { +export const TableSheetFrozenOptions: S2BaseFrozenOptions = { colCount: 1, trailingColCount: 1, }; +export const PivotSheetFrozenOptions: S2BaseFrozenOptions = { + rowCount: 1, + trailingRowCount: 1, +}; + export const S2TooltipOptions: SheetComponentOptions['tooltip'] = { operation: { menu: { @@ -328,6 +341,7 @@ export const s2Options: SheetComponentOptions = { seriesNumber: { enable: false, }, + transformCanvasConfig() { return { supportsCSSTransform: true, diff --git a/packages/s2-react/playground/index.tsx b/packages/s2-react/playground/index.tsx index a178cbabcc..3bea2ca93d 100644 --- a/packages/s2-react/playground/index.tsx +++ b/packages/s2-react/playground/index.tsx @@ -19,6 +19,7 @@ import { type ThemeCfg, type TooltipAutoAdjustBoundary, safeJsonParse, + DEFAULT_FROZEN_COUNTS, } from '@antv/s2'; import type { Adaptive, SheetType } from '@antv/s2-shared'; import corePkg from '@antv/s2/package.json'; @@ -61,6 +62,7 @@ import { StrategySheet } from './components/StrategySheet'; import { BigDataSheet } from './components/BigDataSheet'; import { + PivotSheetFrozenOptions, TableSheetFrozenOptions, defaultOptions, pivotSheetDataCfg, @@ -582,34 +584,10 @@ function MainLayout() { }} disabled={sheetType === 'table'} /> - - { - updateOptions({ - frozen: { - firstRow: checked, - }, - }); - }} - disabled={ - sheetType === 'table' || - (mergedOptions.hierarchyType === 'grid' && - (!mergedOptions?.totals?.row - ?.showGrandTotals || - !mergedOptions?.totals?.row - ?.reverseGrandTotalsLayout)) - } - /> - - - { + if (checked) { + updateOptions({ + frozen: PivotSheetFrozenOptions, + }); + } else { + updateOptions({ + frozen: { ...DEFAULT_FROZEN_COUNTS }, + }); } - onChange={(checked) => { - if (checked) { - updateOptions({ - frozen: TableSheetFrozenOptions, - }); - } else { - updateOptions({ - frozen: { - rowCount: 0, - colCount: 0, - trailingColCount: 0, - trailingRowCount: 0, - }, - }); - } - }} - disabled={sheetType === 'pivot'} - /> - + }} + /> + { + if (checked) { + updateOptions({ + frozen: TableSheetFrozenOptions, + }); + } else { + updateOptions({ + frozen: { ...DEFAULT_FROZEN_COUNTS }, + }); + } + }} + /> ` | +| relatedNode | 给序号列单元格用,标识该序号单元格对应了行头节点,有了关联关系后,就可以在行头冻结时做区分 | [Node[]](/docs/api/basic-class/node) | diff --git a/s2-site/docs/common/frozen.zh.md b/s2-site/docs/common/frozen.zh.md index 421502def8..0e1080a9bf 100644 --- a/s2-site/docs/common/frozen.zh.md +++ b/s2-site/docs/common/frozen.zh.md @@ -7,11 +7,10 @@ order: 1 功能描述:行列头冻结配置。查看 [透视表示例](/examples/layout/frozen/#pivot-frozen-row-header) 和 [明细表示例](/examples/layout/frozen/#table-frozen) -| 参数 | 类型 | 必选 | 默认值 | 功能描述 | -| -- | --- | -- | -- | --- | -| rowHeader | `boolean \| number` | | `true` | 冻结行头
当值为 number 时,标识行头冻结的最大区域,取值范围: (0, 1),0 表示不固定行头。
当值为 boolean 时,true 对应冻结最大区域为 0.5, false 对应 0。
(透视表有效) | -| firstRow | `boolean` | | `false` | 是否冻结首行(适用于总计置于顶部,树状模式等场景,透视表有效)[查看示例](/examples/layout/frozen/#frozen-pivot-grid) | -| rowCount | `number` | | `0` | 冻结行的数量,从顶部开始计数 (明细表有效) | -| colCount | `number` | | `0` | 冻结列的数量,从左侧开始计数 (明细表有效) | -| trailingRowCount | `number` | | `0` | 冻结行数量,从底部开始计数 (明细表有效) | -| trailingColCount | `number` | | `0` | 冻结列的数量,从右侧开始计数 (明细表有效) | +| 参数 | 类型 | 必选 | 默认值 | 功能描述 | +| ---------------- | ------------------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| rowHeader | `boolean \| number` | | `true` | 冻结行头
当值为 number 时,标识行头冻结的最大区域,取值范围: (0, 1),0 表示不固定行头。
当值为 boolean 时,true 对应冻结最大区域为 0.5, false 对应 0。
(透视表有效) | +| rowCount | `number` | | `0` | 冻结行的数量,从顶部开始计数,以叶子节点作为计数依据(透视表在开启行序号,且自定义序号单元格时无效) | +| colCount | `number` | | `0` | 冻结列的数量,从左侧开始计数,以叶子节点作为计数依据 | +| trailingRowCount | `number` | | `0` | 冻结行数量,从底部开始计数,以叶子节点作为计数依据(透视表在开启行序号,且自定义序号单元格时无效) | +| trailingColCount | `number` | | `0` | 冻结列的数量,从右侧开始计数,以叶子节点作为计数依据 | diff --git a/s2-site/docs/manual/basic/sheet-type/pivot-mode.zh.md b/s2-site/docs/manual/basic/sheet-type/pivot-mode.zh.md index 53322a56d6..fa83b26e14 100644 --- a/s2-site/docs/manual/basic/sheet-type/pivot-mode.zh.md +++ b/s2-site/docs/manual/basic/sheet-type/pivot-mode.zh.md @@ -173,9 +173,9 @@ const s2Options = { 支持 [小计/总计](/manual/basic/totals) 的透视能力。 -### 冻结行头 +### 冻结行头区域 -当行头固定时,行头会有一个独立的可滚动区域,如果关闭冻结行头,则滚动区域为整个表格。 +当行头区域固定时,行头会有一个独立的可滚动区域,如果关闭冻结行头,则滚动区域为整个表格。 @@ -201,53 +201,62 @@ const s2Options = { } ``` -### 冻结首行 +### 冻结行、列头单元格 :::info{title="注意"} -目前仅提供**冻结首行**能力,和 [明细表行列冻结](https://s2.antv.antgroup.com/manual/basic/sheet-type/table-mode#%E8%A1%8C%E5%88%97%E5%86%BB%E7%BB%93) 不同,透视表由于带有分组的特性,布局比较复杂,考虑到交互合理性,目前有如下限制: +和 [明细表行列冻结](https://s2.antv.antgroup.com/manual/basic/sheet-type/table-mode#%E8%A1%8C%E5%88%97%E5%86%BB%E7%BB%93) 不同,透视表由于带有分组的特性,布局比较复杂,目前有以下限制: -- 首行不存在子节点(适用于总计置于顶部,只有单个维值,树状模式等场景)。 -- 分页场景暂不支持。 - -`s2Options` 中配置 `frozen.firstRow` 开启首行冻结能力 +- 开启序号列场景,且进行了[自定义序号列布局](/examples/custom/custom-cell/#series-number-cell) 时,暂不支持行头单元格冻结,因为此时,我们无法得知用户序号列和行头单元格的对应关系 ::: -#### 平铺模式 +行列冻结通过在 `s2Options` 中传入这些属性控制: ```ts const s2Options = { - hierarchyType: 'grid', frozen: { - firstRow: true, - }, - // 需要开启行总计 & 总计行置于顶部 - totals: { - row: { - showGrandTotals: true, - reverseLayout: true, - }, + rowCount: number; // 冻结行的叶子节点数量,从顶部开始计数 + trailingRowCount: number; // 冻结行的叶子节点数量,从底部开始计数 + colCount: number; // 冻结列的叶子节点数量,从左侧开始计数 + trailingColCount: number; // 冻结列的叶子节点数量,从右侧开始计数 + } +} +``` + +#### 树状模式 + +```ts +const s2Options = { + hierarchyType: 'tree', + frozen: { + rowCount: 1, }, } ``` - +
-#### 树状模式 +#### 平铺模式 ```ts const s2Options = { - hierarchyType: 'tree', + hierarchyType: 'grid', frozen: { - firstRow: true, + rowCount: 1, + }, + totals: { + row: { + showGrandTotals: true, + reverseLayout: true, + }, }, } ``` - +
diff --git a/s2-site/docs/manual/migration-v2.zh.md b/s2-site/docs/manual/migration-v2.zh.md index 460c3eb52c..40bbb98a69 100644 --- a/s2-site/docs/manual/migration-v2.zh.md +++ b/s2-site/docs/manual/migration-v2.zh.md @@ -53,11 +53,11 @@ $ npm install @antv/s2-react@next antd @ant-design/icons --save $ npm install @antv/s2-vue@next ant-design-vue@3.x --save ``` -| Package | Version | Size | Download | -| - | - | - | - | -| [@antv/s2](https://github.com/antvis/S2/tree/next/packages/s2-core) | ![next](https://img.shields.io/npm/v/@antv/s2/next.svg) | ![size](https://img.badgesize.io/https:/unpkg.com/@antv/s2@next/dist/index.min.js?label=gzip%20size&compression=gzip) | ![download](https://img.shields.io/npm/dm/@antv/s2.svg) | -| [@antv/s2-react](https://github.com/antvis/S2/tree/next/packages/s2-react) | ![next](https://img.shields.io/npm/v/@antv/s2-react/next.svg)| ![size](https://img.badgesize.io/https:/unpkg.com/@antv/s2-react@next/dist/index.min.js?label=gzip%20size&compression=gzip) | ![download](https://img.shields.io/npm/dm/@antv/s2-react.svg) | -| [@antv/s2-vue](https://github.com/antvis/S2/tree/next/packages/s2-vue) | ![next](https://img.shields.io/npm/v/@antv/s2-vue/next.svg) | ![size](https://img.badgesize.io/https:/unpkg.com/@antv/s2-vue@next/dist/index.min.js?label=gzip%20size&compression=gzip) | ![download](https://img.shields.io/npm/dm/@antv/s2-vue.svg) | +| Package | Version | Size | Download | +| -------------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | +| [@antv/s2](https://github.com/antvis/S2/tree/next/packages/s2-core) | ![next](https://img.shields.io/npm/v/@antv/s2/next.svg) | ![size](https://img.badgesize.io/https:/unpkg.com/@antv/s2@next/dist/index.min.js?label=gzip%20size&compression=gzip) | ![download](https://img.shields.io/npm/dm/@antv/s2.svg) | +| [@antv/s2-react](https://github.com/antvis/S2/tree/next/packages/s2-react) | ![next](https://img.shields.io/npm/v/@antv/s2-react/next.svg) | ![size](https://img.badgesize.io/https:/unpkg.com/@antv/s2-react@next/dist/index.min.js?label=gzip%20size&compression=gzip) | ![download](https://img.shields.io/npm/dm/@antv/s2-react.svg) | +| [@antv/s2-vue](https://github.com/antvis/S2/tree/next/packages/s2-vue) | ![next](https://img.shields.io/npm/v/@antv/s2-vue/next.svg) | ![size](https://img.badgesize.io/https:/unpkg.com/@antv/s2-vue@next/dist/index.min.js?label=gzip%20size&compression=gzip) | ![download](https://img.shields.io/npm/dm/@antv/s2-vue.svg) | ## ⭐ 新增功能 @@ -401,24 +401,25 @@ const s2DataConfig = { #### 行列冻结配置 -透视表和明细表的行列冻结配置统一收拢到 `frozen`. +透视表和明细表的行列冻结配置统一收拢到 `frozen`。 + +在透视表中,`frozenFirstRow` 使用 `rowCount: 1` 替代。在明细表多级列头冻结时,在 `1.x` 中是以最最顶层节点为准,而在 `2.x` 是以叶子节点的数量为准。 ```diff const s2Options = { - frozenRowHeader: true, - frozenFirstRow: true, -- frozenRowCount: true; -- frozenColCount: true; -- frozenTrailingRowCount: true; -- frozenTrailingColCount: true; +- frozenRowCount: 1; +- frozenColCount: 1; +- frozenTrailingRowCount: 1; +- frozenTrailingColCount: 1; + frozen: { + rowHeader: true, -+ firstRow: true, -+ rowCount: true; -+ colCount: true; -+ trailingRowCount: true; -+ trailingColCount: true; ++ rowCount: 1; ++ colCount: 1; ++ trailingRowCount: 1; ++ trailingColCount: 1; + } } ``` diff --git a/s2-site/examples/layout/frozen/demo/frozen-pivot-grid.ts b/s2-site/examples/layout/frozen/demo/frozen-pivot-grid.ts index 439e2c0876..bd73cc097f 100644 --- a/s2-site/examples/layout/frozen/demo/frozen-pivot-grid.ts +++ b/s2-site/examples/layout/frozen/demo/frozen-pivot-grid.ts @@ -11,7 +11,10 @@ fetch( width: 600, height: 300, frozen: { - firstRow: true, + rowCount: 1, + trailingRowCount: 1, + colCount: 1, + trailingColCount: 1, }, totals: { row: { @@ -19,6 +22,14 @@ fetch( reverseGrandTotalsLayout: true, }, }, + style: { + colCell: { + widthByField: { + 'root[&]家具[&]沙发[&]number': 200, + 'root[&]办公用品[&]笔[&]number': 200, + }, + }, + }, }; const s2 = new PivotSheet(container, dataCfg, s2Options); diff --git a/s2-site/examples/layout/frozen/demo/frozen-pivot-series.ts b/s2-site/examples/layout/frozen/demo/frozen-pivot-series.ts new file mode 100644 index 0000000000..755cf39f0c --- /dev/null +++ b/s2-site/examples/layout/frozen/demo/frozen-pivot-series.ts @@ -0,0 +1,70 @@ +import { PivotSheet, S2Options, Node } from '@antv/s2'; + +const layoutSeriesNumberNodes: S2Options['layoutSeriesNumberNodes'] = ( + rowsHierarchy, + seriesNumberWidth, + spreadsheet, +) => { + const isHierarchyTreeType = spreadsheet.isHierarchyTreeType(); + const leaves = isHierarchyTreeType + ? rowsHierarchy.getNodes() + : rowsHierarchy.getLeaves(); + + return leaves.map((node, idx) => { + const sNode = new Node({ + id: '', + field: '', + rowIndex: idx, + // 序号从 a 开始递增,在大量数据下,需要更完善的处理 + value: `${String.fromCharCode(97 + idx)}`, + }); + + sNode.x = 0; + sNode.y = node.y; + sNode.width = seriesNumberWidth; + sNode.height = node.height; + + return sNode; + }); +}; + +fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', +) + .then((res) => res.json()) + .then(async (dataCfg) => { + const container = document.getElementById('container'); + + const s2Options: S2Options = { + width: 600, + height: 300, + seriesNumber: { + enable: true, + }, + layoutSeriesNumberNodes, // 开启自定义序号列时,行冻结失效,注释掉试试看 + frozen: { + rowCount: 1, + trailingRowCount: 1, + colCount: 1, + trailingColCount: 1, + }, + totals: { + row: { + showGrandTotals: true, + reverseGrandTotalsLayout: true, + }, + }, + style: { + colCell: { + widthByField: { + 'root[&]家具[&]沙发[&]number': 200, + 'root[&]办公用品[&]笔[&]number': 200, + }, + }, + }, + }; + + const s2 = new PivotSheet(container, dataCfg, s2Options); + + await s2.render(); + }); diff --git a/s2-site/examples/layout/frozen/demo/frozen-pivot-tree.ts b/s2-site/examples/layout/frozen/demo/frozen-pivot-tree.ts index 5e491fe1d3..a675899a36 100644 --- a/s2-site/examples/layout/frozen/demo/frozen-pivot-tree.ts +++ b/s2-site/examples/layout/frozen/demo/frozen-pivot-tree.ts @@ -12,7 +12,18 @@ fetch( height: 300, hierarchyType: 'tree', frozen: { - firstRow: true, + rowCount: 1, + trailingRowCount: 1, + colCount: 1, + trailingColCount: 1, + }, + style: { + colCell: { + widthByField: { + 'root[&]家具[&]沙发[&]number': 200, + 'root[&]办公用品[&]笔[&]number': 200, + }, + }, }, }; diff --git a/s2-site/examples/layout/frozen/demo/meta.json b/s2-site/examples/layout/frozen/demo/meta.json index 6c631b8107..056956e622 100644 --- a/s2-site/examples/layout/frozen/demo/meta.json +++ b/s2-site/examples/layout/frozen/demo/meta.json @@ -25,19 +25,28 @@ { "filename": "frozen-pivot-grid.ts", "title": { - "zh": "透视表 - 平铺模式冻结首行", - "en": "pivot mode freezes head rows" + "zh": "透视表 - 平铺模式冻结", + "en": "pivot mode freezes" }, - "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ge0_S5iMB-wAAAAAAAAAAAAADmJ7AQ/original", + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*zhWJT5qdlPUAAAAAAAAAAAAADmJ7AQ/original", "new": true }, { "filename": "frozen-pivot-tree.ts", "title": { - "zh": "透视表 - 树状模式冻结首行", - "en": "tree mode freezes head rows" + "zh": "透视表 - 树状模式冻结", + "en": "tree mode freezes" }, - "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ncdCT7NB2I0AAAAAAAAAAAAADmJ7AQ/original", + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*BVmlRZ2ga5sAAAAAAAAAAAAADmJ7AQ/original", + "new": true + }, + { + "filename": "frozen-pivot-series.ts", + "title": { + "zh": "透视表 - 序号列冻结", + "en": "pivot mode series number freezes" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*MHR_TLO1s2wAAAAAAAAAAAAADmJ7AQ/original", "new": true } ]