From 7431ab73db6be3fb8b7e845c8699aa5212d371e1 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Sun, 25 Jun 2023 19:39:46 +0800 Subject: [PATCH 01/11] feat: remove column group theme function --- .../scenegraph/group-creater/cell-helper.ts | 23 ++- .../group-creater/cell-type/chart-cell.ts | 6 +- .../group-creater/cell-type/image-cell.ts | 4 +- .../cell-type/spark-line-cell.ts | 7 +- .../group-creater/cell-type/text-cell.ts | 131 +++++++++--------- .../group-creater/cell-type/video-cell.ts | 4 +- .../scenegraph/group-creater/column-helper.ts | 67 ++------- .../src/scenegraph/layout/update-width.ts | 4 +- .../src/scenegraph/utils/text-icon-layout.ts | 12 +- packages/vtable/src/themes/DEFAULT.ts | 2 +- 10 files changed, 107 insertions(+), 153 deletions(-) diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index f2843916f..643cc0aca 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -43,10 +43,9 @@ export function createCell( textAlign: CanvasTextAlign, textBaseline: CanvasTextBaseline, mayHaveIcon: boolean, - isfunctionalProps: boolean, isMerge: boolean, range: CellRange, - cellTheme?: IThemeSpec + cellTheme: IThemeSpec ): Group { let cellGroup: Group; if (type === 'text' || type === 'link') { @@ -126,7 +125,6 @@ export function createCell( textAlign, textBaseline, mayHaveIcon, - isfunctionalProps, renderDefault, cellTheme ); @@ -192,7 +190,8 @@ export function createCell( (define as ChartColumnDefine).chartType, (define as ChartColumnDefine).chartSpec, (columnGroup.attribute as any).chartInstance, - table + table, + cellTheme ); } else if (type === 'progressbar') { const style = table._getCellStyle(col, row) as ProgressBarStyle; @@ -214,7 +213,6 @@ export function createCell( textBaseline, false, true, - true, cellTheme ); @@ -233,7 +231,19 @@ export function createCell( // 进度图插入到文字前,绘制在文字下 cellGroup.insertBefore(progressBarGroup, cellGroup.firstChild); } else if (type === 'sparkline') { - cellGroup = createSparkLineCellGroup(null, columnGroup, 0, y, col, row, cellWidth, cellHeight, padding, table); + cellGroup = createSparkLineCellGroup( + null, + columnGroup, + 0, + y, + col, + row, + cellWidth, + cellHeight, + padding, + table, + cellTheme + ); } return cellGroup; @@ -330,7 +340,6 @@ export function updateCell(col: number, row: number, table: BaseTableAPI) { textAlign, textBaseline, mayHaveIcon, - false, isMerge, range, cellTheme diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/chart-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/chart-cell.ts index f0b1dc762..ea80c9328 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/chart-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/chart-cell.ts @@ -1,10 +1,10 @@ import { Group } from '../../graphic/group'; -import { getCellTheme } from './text-cell'; import { Chart } from '../../graphic/chart'; import * as registerChartTypes from '../../../chartType'; import { getFunctionalProp } from '../../utils/get-prop'; import { isValid } from '../../../tools/util'; import type { BaseTableAPI } from '../../../ts-types/base-table'; +import type { IThemeSpec } from '@visactor/vrender'; export function createChartCellGroup( cellGroup: Group | null, columnGroup: Group, @@ -19,12 +19,12 @@ export function createChartCellGroup( chartType: any, chartSpec: any, chartInstance: any, - table: BaseTableAPI + table: BaseTableAPI, + cellTheme: IThemeSpec ) { // 获取注册的chart图表类型 const registerCharts = registerChartTypes.get(); const ClassType = registerCharts[chartType]; - const cellTheme = getCellTheme(table, col, row); const headerStyle = table._getCellStyle(col, row); // to be fixed const functionalPadding = getFunctionalProp('padding', headerStyle, col, row, table); if (isValid(functionalPadding)) { diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts index 293650d71..8be9e7698 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts @@ -9,7 +9,6 @@ import { calcKeepAspectRatioSize } from '../../utils/keep-aspect-ratio'; import { calcStartPosition } from '../../utils/cell-pos'; import type { Scenegraph } from '../../scenegraph'; import { getProp, getFunctionalProp } from '../../utils/get-prop'; -import { getCellTheme } from './text-cell'; import { isValid } from '../../../tools/util'; import { getPadding } from '../../utils/padding'; @@ -29,9 +28,8 @@ export function createImageCellGroup( textAlign: CanvasTextAlign, textBaseline: CanvasTextBaseline, table: BaseTableAPI, - cellTheme?: IThemeSpec + cellTheme: IThemeSpec ) { - cellTheme = getCellTheme(table, col, row, cellTheme); const headerStyle = table._getCellStyle(col, row); // to be fixed const functionalPadding = getFunctionalProp('padding', headerStyle, col, row, table); // const margin = getProp('padding', headerStyle, col, row, table); diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/spark-line-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/spark-line-cell.ts index c129ec09d..672e7901f 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/spark-line-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/spark-line-cell.ts @@ -1,10 +1,9 @@ -import type { ILine, ISymbol } from '@visactor/vrender'; +import type { ILine, ISymbol, IThemeSpec } from '@visactor/vrender'; import { createLine, createSymbol } from '@visactor/vrender'; import { PointScale, LinearScale } from '@visactor/vscale'; import { isValid } from '../../../tools/util'; import { Group } from '../../graphic/group'; import type { CellInfo, SparklineSpec } from '../../../ts-types'; -import { getCellTheme } from './text-cell'; import type { BaseTableAPI } from '../../../ts-types/base-table'; const xScale: PointScale = new PointScale(); @@ -20,9 +19,9 @@ export function createSparkLineCellGroup( width: number, height: number, padding: number[], - table: BaseTableAPI + table: BaseTableAPI, + cellTheme: IThemeSpec ) { - const cellTheme = getCellTheme(table, col, row); // cell if (!cellGroup) { cellGroup = new Group({ diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts index 961653eb8..aa2d64bca 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts @@ -40,14 +40,9 @@ export function createCellGroup( textAlign: CanvasTextAlign, textBaseline: CanvasTextBaseline, mayHaveIcon: boolean, - isfunctionalProps: boolean, renderDefault: boolean, - cellTheme?: IThemeSpec + cellTheme: IThemeSpec ): Group { - // 处理函数样式 - if (isfunctionalProps) { - cellTheme = getCellTheme(table, col, row, cellTheme); - } const headerStyle = table._getCellStyle(col, row); // to be fixed const functionalPadding = getFunctionalProp('padding', headerStyle, col, row, table); if (isValid(functionalPadding)) { @@ -132,65 +127,65 @@ export function createCellGroup( return cellGroup; } -/** - * @description: 获取函数式赋值的样式,记录在cellTheme中 - * @param {BaseTableAPI} table - * @param {number} col - * @param {number} row - * @param {IThemeSpec} cellTheme - * @return {IThemeSpec | undefined} - */ -export function getCellTheme( - table: BaseTableAPI, - col: number, - row: number, - cellTheme?: IThemeSpec -): IThemeSpec | undefined { - // get column header style - const headerStyle = table._getCellStyle(col, row); - - const theme = getStyleTheme(headerStyle, table, col, row, getFunctionalProp).theme; - - for (const prop in theme.group) { - if (isValid(theme.group[prop])) { - if (!cellTheme) { - cellTheme = {}; - } - - if (!cellTheme.group) { - cellTheme.group = {}; - } - - cellTheme.group[prop] = theme.group[prop]; - } - } - - for (const prop in theme.text) { - if (isValid(theme.text[prop])) { - if (!cellTheme) { - cellTheme = {}; - } - - if (!cellTheme.text) { - cellTheme.text = {}; - } - - cellTheme.text[prop] = theme.text[prop]; - } - } - - for (const prop in theme._vtable) { - if (isValid(theme._vtable[prop])) { - if (!cellTheme) { - cellTheme = {}; - } - - if (!(cellTheme as any)._vtable) { - (cellTheme as any)._vtable = {}; - } - - (cellTheme as any)._vtable[prop] = theme._vtable[prop]; - } - } - return cellTheme; -} +// /** +// * @description: 获取函数式赋值的样式,记录在cellTheme中 +// * @param {BaseTableAPI} table +// * @param {number} col +// * @param {number} row +// * @param {IThemeSpec} cellTheme +// * @return {IThemeSpec | undefined} +// */ +// export function getCellTheme( +// table: BaseTableAPI, +// col: number, +// row: number, +// cellTheme?: IThemeSpec +// ): IThemeSpec | undefined { +// // get column header style +// const headerStyle = table._getCellStyle(col, row); + +// const theme = getStyleTheme(headerStyle, table, col, row, getFunctionalProp).theme; + +// for (const prop in theme.group) { +// if (isValid(theme.group[prop])) { +// if (!cellTheme) { +// cellTheme = {}; +// } + +// if (!cellTheme.group) { +// cellTheme.group = {}; +// } + +// cellTheme.group[prop] = theme.group[prop]; +// } +// } + +// for (const prop in theme.text) { +// if (isValid(theme.text[prop])) { +// if (!cellTheme) { +// cellTheme = {}; +// } + +// if (!cellTheme.text) { +// cellTheme.text = {}; +// } + +// cellTheme.text[prop] = theme.text[prop]; +// } +// } + +// for (const prop in theme._vtable) { +// if (isValid(theme._vtable[prop])) { +// if (!cellTheme) { +// cellTheme = {}; +// } + +// if (!(cellTheme as any)._vtable) { +// (cellTheme as any)._vtable = {}; +// } + +// (cellTheme as any)._vtable[prop] = theme._vtable[prop]; +// } +// } +// return cellTheme; +// } diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts index 03f739971..4f9552911 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts @@ -9,7 +9,6 @@ import { calcStartPosition } from '../../utils/cell-pos'; import { _adjustWidthHeight } from './image-cell'; import { getFunctionalProp, getProp } from '../../utils/get-prop'; import { isValid } from '../../../tools/util'; -import { getCellTheme } from './text-cell'; import type { BaseTableAPI } from '../../../ts-types/base-table'; const regedIcons = icons.get(); @@ -28,9 +27,8 @@ export function createVideoCellGroup( textAlign: CanvasTextAlign, textBaseline: CanvasTextBaseline, table: BaseTableAPI, - cellTheme?: IThemeSpec + cellTheme: IThemeSpec ) { - cellTheme = getCellTheme(table, col, row, cellTheme); const headerStyle = table._getCellStyle(col, row); // to be fixed const functionalPadding = getFunctionalProp('padding', headerStyle, col, row, table); // const margin = getProp('padding', headerStyle, col, row, table); diff --git a/packages/vtable/src/scenegraph/group-creater/column-helper.ts b/packages/vtable/src/scenegraph/group-creater/column-helper.ts index 31c2bcca9..71ba38139 100644 --- a/packages/vtable/src/scenegraph/group-creater/column-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/column-helper.ts @@ -51,46 +51,8 @@ export function createComplexColumn( let padding; let textAlign; let textBaseline; - let isfunctionalProps = false; /** useColumnTheme 判断是否可以使用columnTheme */ - const useColumnTheme = - ((table.isListTable() && !table.scenegraph.transpose) || - (table.isPivotTable() && (table.internalProps.layoutMap as PivotHeaderLayoutMap).indicatorsAsCol)) && - cellType === 'body'; - if (useColumnTheme) { - if (!columnGroup.childrenCount) { - // 只在首屏生效 - const { theme: columnTheme, hasFunctionPros } = getColumnGroupTheme(col, colWidth, table); - isfunctionalProps = hasFunctionPros; - // get column header style - if (columnTheme._vtable.padding) { - padding = columnTheme._vtable.padding; - } - if (columnTheme.text.textAlign) { - textAlign = columnTheme.text.textAlign; - } - if (columnTheme.text.textBaseline) { - textBaseline = columnTheme.text.textBaseline; - } - columnGroup.setTheme(columnTheme); - } else if (columnGroup.theme) { - const style = table._getCellStyle(col, table.columnHeaderLevelCount); // to be fixed - const { hasFunctionPros } = getStyleTheme(style, table, col, table.columnHeaderLevelCount, getRawProp, false); - isfunctionalProps = hasFunctionPros; - const columnTheme = columnGroup.theme.userTheme as any; - // 渐进加载时获取 - if (columnTheme._vtable.padding) { - padding = columnTheme._vtable.padding; - } - if (columnTheme.text.textAlign) { - textAlign = columnTheme.text.textAlign; - } - if (columnTheme.text.textBaseline) { - textBaseline = columnTheme.text.textBaseline; - } - } - } let bgColorFunc: Function; // 判断是否有mapping 遍历dataset中mappingRules if ((table.internalProps as PivotTableProtected)?.dataConfig?.mappingRules && cellType === 'body') { @@ -112,25 +74,21 @@ export function createComplexColumn( const row = j; const define = cellType !== 'body' ? table.getHeaderDefine(col, row) : table.getBodyColumnDefine(col, row); const mayHaveIcon = cellType !== 'body' ? true : !!define?.icon; - let cellTheme; - if (!useColumnTheme) { - const headerStyle = table._getCellStyle(col, row); - cellTheme = getStyleTheme(headerStyle, table, col, row, getProp).theme; - cellTheme.group.width = colWidth; - cellTheme.group.height = Array.isArray(defaultRowHeight) ? defaultRowHeight[row] : defaultRowHeight; - if (cellTheme._vtable.padding) { - padding = cellTheme._vtable.padding; - } - if (cellTheme.text.textAlign) { - textAlign = cellTheme.text.textAlign; - } - if (cellTheme.text.textBaseline) { - textBaseline = cellTheme.text.textBaseline; - } + const headerStyle = table._getCellStyle(col, row); + const cellTheme = getStyleTheme(headerStyle, table, col, row, getProp).theme; + cellTheme.group.width = colWidth; + cellTheme.group.height = Array.isArray(defaultRowHeight) ? defaultRowHeight[row] : defaultRowHeight; + if (cellTheme._vtable.padding) { + padding = cellTheme._vtable.padding; + } + if (cellTheme.text.textAlign) { + textAlign = cellTheme.text.textAlign; + } + if (cellTheme.text.textBaseline) { + textBaseline = cellTheme.text.textBaseline; } // margin = getProp('margin', headerStyle, col, 0, table) - let cellGroup; let cellWidth = colWidth; let cellHeight = table.internalProps.autoRowHeight ? 0 : table.getRowHeight(row); const type = @@ -209,7 +167,6 @@ export function createComplexColumn( textAlign, textBaseline, mayHaveIcon, - isfunctionalProps, isMerge, range, cellTheme diff --git a/packages/vtable/src/scenegraph/layout/update-width.ts b/packages/vtable/src/scenegraph/layout/update-width.ts index 1bee2ac79..a5095c408 100644 --- a/packages/vtable/src/scenegraph/layout/update-width.ts +++ b/packages/vtable/src/scenegraph/layout/update-width.ts @@ -1,4 +1,5 @@ import type { ProgressBarStyle } from '../../body-helper/style/ProgressBarStyle'; +import { getStyleTheme } from '../../core/tableHelper'; import type { IProgressbarColumnBodyDefine } from '../../ts-types/list-table/define/progressbar-define'; import type { Group } from '../graphic/group'; import type { Icon } from '../graphic/icon'; @@ -252,7 +253,8 @@ function updateCellWidth( cellGroup.attribute.width, cellGroup.attribute.height, padding, - scene.table + scene.table, + getStyleTheme(headerStyle, scene.table, col, row, getProp).theme ); } else if (type === 'image' || type === 'video') { // // 只更新背景边框 diff --git a/packages/vtable/src/scenegraph/utils/text-icon-layout.ts b/packages/vtable/src/scenegraph/utils/text-icon-layout.ts index 69832281f..3f12bf10e 100644 --- a/packages/vtable/src/scenegraph/utils/text-icon-layout.ts +++ b/packages/vtable/src/scenegraph/utils/text-icon-layout.ts @@ -46,7 +46,7 @@ export function createCellContent( cellHeight: number, textAlign: CanvasTextAlign, textBaseline: CanvasTextBaseline, - cellTheme?: IThemeSpec + cellTheme: IThemeSpec ) { const leftIcons: ColumnIconOption[] = []; const rightIcons: ColumnIconOption[] = []; @@ -82,9 +82,7 @@ export function createCellContent( heightLimit: autoRowHeight ? -1 : cellHeight - (padding[0] + padding[2]), pickable: false }; - const wrapText = new WrapText( - cellTheme && cellTheme.text ? (Object.assign({}, cellTheme.text, attribute) as any) : attribute - ); + const wrapText = new WrapText(cellTheme.text ? (Object.assign({}, cellTheme.text, attribute) as any) : attribute); wrapText.name = 'text'; cellGroup.appendChild(wrapText); @@ -182,9 +180,7 @@ export function createCellContent( autoWrapText, lineClamp }; - const wrapText = new WrapText( - cellTheme && cellTheme.text ? (Object.assign({}, cellTheme.text, attribute) as any) : attribute - ); + const wrapText = new WrapText(cellTheme.text ? (Object.assign({}, cellTheme.text, attribute) as any) : attribute); wrapText.name = 'text'; textMark = wrapText; } else { @@ -194,7 +190,7 @@ export function createCellContent( }, (cellGroup.parent as Group)?.theme?.userTheme?.text || {} ); - if (cellTheme && cellTheme.text) { + if (cellTheme.text) { Object.assign(textOption, cellTheme.text); } textOption.textBaseline = 'middle'; diff --git a/packages/vtable/src/themes/DEFAULT.ts b/packages/vtable/src/themes/DEFAULT.ts index 45f87e1c4..6de069cd9 100644 --- a/packages/vtable/src/themes/DEFAULT.ts +++ b/packages/vtable/src/themes/DEFAULT.ts @@ -67,7 +67,7 @@ export default { }, bodyStyle: { fontSize: 14, - bgColor: getBackgroundColor, + bgColor: 'white', hover: { // cellBorderColor: "#003fff", cellBgColor: '#CCE0FF', From 5eab02673bfa80a11e6e6231b9349602349ce204 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 26 Jun 2023 18:16:51 +0800 Subject: [PATCH 02/11] feat: add compute-row-height function --- packages/vtable/src/core/BaseTable.ts | 24 +- .../group-creater/progress/proxy.ts | 7 + .../scenegraph/layout/compute-col-width.ts | 2 +- .../scenegraph/layout/compute-row-height.ts | 273 ++++++++++++++++++ packages/vtable/src/themes/DEFAULT.ts | 2 +- packages/vtable/src/tools/NumberMap.ts | 5 + packages/vtable/src/ts-types/base-table.ts | 1 + 7 files changed, 302 insertions(+), 12 deletions(-) create mode 100644 packages/vtable/src/scenegraph/layout/compute-row-height.ts diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 02884036c..452f7f13b 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -1009,17 +1009,21 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { } /** * 清空含有指定row的缓存 - * @param col + * @param row */ - _clearRowRangeHeightsMap(row: number): void { - const keys = this._rowRangeHeightsMap.keys(); - for (const key of keys) { - const reg = rangeReg.exec(key); - if (reg) { - const start = Number(reg[1]); - const end = Number(reg[2]); - if (row >= start && row <= end) { - this._rowRangeHeightsMap.delete(key); + _clearRowRangeHeightsMap(row?: number): void { + if (typeof row !== 'number') { + this._rowRangeHeightsMap.clear(); + } else { + const keys = this._rowRangeHeightsMap.keys(); + for (const key of keys) { + const reg = rangeReg.exec(key); + if (reg) { + const start = Number(reg[1]); + const end = Number(reg[2]); + if (row >= start && row <= end) { + this._rowRangeHeightsMap.delete(key); + } } } } diff --git a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts index 3bef8dceb..fce91b31b 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts @@ -14,6 +14,7 @@ const mergeMap = new Map(); export class SceneProxy { table: BaseTableAPI; scenegraph: Scenegraph; + mode: 'column' | 'row' | 'pivot' = 'column'; currentRow = 0; totalRow: number; rowLimit = 1000; @@ -44,6 +45,12 @@ export class SceneProxy { constructor(table: BaseTableAPI) { this.table = table; this.scenegraph = table.scenegraph; + + if (this.table.internalProps.transpose) { + this.mode = 'row'; + } else if (this.table.isPivotTable()) { + this.mode = 'pivot'; + } } setParams() { diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts index 76ec8e398..00350cc1f 100644 --- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts +++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts @@ -346,7 +346,7 @@ function computeTextWidth(col: number, row: number, table: BaseTableAPI): number return maxWidth; } -function getCellRect(col: number, row: number, table: BaseTableAPI) { +export function getCellRect(col: number, row: number, table: BaseTableAPI) { return { left: 0, top: 0, diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts new file mode 100644 index 000000000..c4654add4 --- /dev/null +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -0,0 +1,273 @@ +import { RichText } from '@visactor/vrender'; +import type { PivotHeaderLayoutMap } from '../../layout/pivot-header-layout'; +import { validToString } from '../../tools/util'; +import type { ColumnIconOption } from '../../ts-types'; +import { IconPosition } from '../../ts-types'; +import type { BaseTableAPI, HeaderData } from '../../ts-types/base-table'; +import type { ColumnData, TextColumnDefine } from '../../ts-types/list-table/layout-map/api'; +import { WrapText } from '../graphic/text'; +import { getProp } from '../utils/get-prop'; +import { getPadding } from '../utils/padding'; +import { getCellRect } from './compute-col-width'; + +const utilTextMark = new WrapText({}); +const utilRichTextMark = new RichText({ + width: 0, + height: 0, + textConfig: [] +}); + +export function computeRowsHeight(table: BaseTableAPI): void { + if (!table.internalProps.autoRowHeight) { + // autoRowHeight false use default height + return; + } + const time = typeof window !== 'undefined' ? window.performance.now() : 0; + table._clearRowRangeHeightsMap(); + // table.rowHeightsMap.clear(); + table.internalProps._rowHeightsMap.clear(); + + // compute header row in column header row + for (let row = 0; row < table.columnHeaderLevelCount; row++) { + const height = computeRowHeight(row, 0, table.colCount - 1, table); + table.setRowHeight(row, height); + } + + // compute body row + if ( + !table.internalProps.transpose && + !(table.isPivotTable() && (table.internalProps.layoutMap as PivotHeaderLayoutMap).indicatorsAsCol) && + checkFixedStyleAndNoWrap(table) + ) { + // check fixed style and no wrap situation, fill all row width single compute + // traspose table and row indicator pivot table cannot use single row height + const height = computeRowHeight(table.columnHeaderLevelCount, 0, table.colCount - 1, table); + fillRowsHeight(height, table.columnHeaderLevelCount, table.rowCount - 1, table); + } else if ( + (table.internalProps.transpose || + (table.isPivotTable() && !(table.internalProps.layoutMap as PivotHeaderLayoutMap).indicatorsAsCol)) && + checkFixedStyleAndNoWrap(table) + ) { + // check fixed style and no wrap situation, just compute 0-table.rowHeaderLevelCount column(the column after row header) in ervey row + // in traspose table and row indicator pivot table + for (let row = table.columnHeaderLevelCount; row < table.rowCount; row++) { + const height = computeRowHeight(row, 0, table.rowHeaderLevelCount, table); + table.setRowHeight(row, height); + } + } else { + for (let row = table.columnHeaderLevelCount; row < table.rowCount; row++) { + const height = computeRowHeight(row, 0, table.colCount - 1, table); + table.setRowHeight(row, height); + } + } + + console.log('computeColsWidth time:', (typeof window !== 'undefined' ? window.performance.now() : 0) - time); +} + +export function computeRowHeight(row: number, startCol: number, endCol: number, table: BaseTableAPI): number { + let maxHeight = 0; + for (let col = startCol; col <= endCol; col++) { + // CustomRender height calculation + const customHeight = computeCustomRenderHeight(col, row, table); + if (typeof customHeight === 'number') { + maxHeight = Math.max(customHeight, maxHeight); + continue; + } + + // text height calculation + const textHeight = computeTextHeight(col, row, table); + maxHeight = Math.max(textHeight, maxHeight); + } + return maxHeight; +} + +function checkFixedStyleAndNoWrap(table: BaseTableAPI): boolean { + const { layoutMap } = table.internalProps; + const row = table.columnHeaderLevelCount; + for (let col = 0; col < table.colCount; col++) { + const isHeader = layoutMap.isHeader(col, row); + const cellDefine = isHeader ? layoutMap.getHeader(col, row) : layoutMap.getBody(col, row); + if ( + typeof cellDefine.style === 'function' || + typeof (cellDefine as ColumnData).icon === 'function' || + typeof (cellDefine as HeaderData).headerIcon === 'function' || + (isHeader ? cellDefine.define?.headerCustomRender : cellDefine.define?.customRender) || + (isHeader ? typeof cellDefine.define?.headerIcon === 'function' : typeof cellDefine.define?.icon === 'function') + ) { + return false; + } + const cellStyle = table._getCellStyle(col, row); + if ( + typeof cellStyle.padding === 'function' || + typeof cellStyle.fontSize === 'function' || + typeof cellStyle.lineHeight === 'function' || + cellStyle.autoWrapText === true + ) { + return false; + } + } + + return true; +} + +function fillRowsHeight(height: number, startRow: number, endRow: number, table: BaseTableAPI) { + for (let row = startRow; row <= endRow; row++) { + table.setRowHeight(row, height); + } +} + +/** + * @description: compute customRender height + * @param {number} col + * @param {number} row + * @param {BaseTableAPI} table + * @return {*} + */ +function computeCustomRenderHeight(col: number, row: number, table: BaseTableAPI): number | undefined { + const customRender = table.getCustomRender(col, row); + const customLayout = table.getCustomLayout(col, row); + if (customRender || customLayout) { + let spanCol = 1; + let height = 0; + if (table.isHeader(col, row) || (table.getBodyColumnDefine(col, row) as TextColumnDefine).mergeCell) { + const cellRange = table.getCellRange(col, row); + spanCol = cellRange.end.col - cellRange.start.col + 1; + } + const arg = { + col, + row, + dataValue: table.getCellOriginValue(col, row), + value: table.getCellValue(col, row) || '', + rect: getCellRect(col, row, table), + table + }; + if (customLayout) { + // 处理customLayout + const customLayoutObj = customLayout(arg); + customLayoutObj.rootContainer.isRoot = true; + const size = customLayoutObj.rootContainer.getContentSize(); + height = size.height ?? 0; + } else if (typeof customRender === 'function') { + // 处理customRender + const customRenderObj = customRender(arg); + height = customRenderObj?.expectedHeight ?? 0; + } else { + height = customRender?.expectedHeight ?? 0; + } + return height / spanCol; + } + return undefined; +} + +/** + * @description: compute text height + * @param {number} col + * @param {number} row + * @param {BaseTableAPI} table + * @return {*} + */ +function computeTextHeight(col: number, row: number, table: BaseTableAPI): number { + let maxHeight = 0; + const cellValue = table.getCellValue(col, row); + // const dataValue = table.getCellOriginValue(col, row); + const actStyle = table._getCellStyle(col, row); + let iconHeight = 0; + let iconWidth = 0; + const iconInlineFront: ColumnIconOption[] = []; + let iconInlineFrontHeight = 0; + const iconInlineEnd: ColumnIconOption[] = []; + let iconInlineEndHeight = 0; + const mayHaveIcon = table.getCellType(col, row) !== 'body' ? true : !!table.getBodyColumnDefine(col, row)?.icon; + if (mayHaveIcon) { + const icons = table.getCellIcons(col, row); + icons?.forEach(icon => { + if ( + icon.positionType !== IconPosition.absoluteRight && + icon.positionType !== IconPosition.inlineFront && + icon.positionType !== IconPosition.inlineEnd + ) { + iconWidth += (icon.width ?? 0) + (icon.marginLeft ?? 0) + (icon.marginRight ?? 0); + iconHeight = Math.max(iconHeight, (icon.height ?? 0) + (icon.marginLeft ?? 0) + (icon.marginRight ?? 0)); + } else if (icon.positionType === IconPosition.inlineFront) { + iconInlineFront.push(icon); + iconInlineFrontHeight = Math.max( + iconInlineFrontHeight, + (icon.height ?? 0) + (icon.marginLeft ?? 0) + (icon.marginRight ?? 0) + ); + } else if (icon.positionType === IconPosition.inlineEnd) { + iconInlineEnd.push(icon); + iconInlineEndHeight = Math.max( + iconInlineEndHeight, + (icon.height ?? 0) + (icon.marginLeft ?? 0) + (icon.marginRight ?? 0) + ); + } + }); + } + let spanRow = 1; + if (table.isHeader(col, row) || (table.getBodyColumnDefine(col, row) as TextColumnDefine).mergeCell) { + const cellRange = table.getCellRange(col, row); + spanRow = cellRange.end.row - cellRange.start.row + 1; + } + + const padding = getPadding(getProp('padding', actStyle, col, row, table)); + const fontSize = getProp('fontSize', actStyle, col, row, table); + const lineHeight = getProp('lineHeight', actStyle, col, row, table) ?? fontSize; + const fontFamily = getProp('fontFamily', actStyle, col, row, table); + const autoWrapText = getProp('autoWrapText', actStyle, col, row, table); + const lines = validToString(cellValue).split('\n') || []; + + if (iconInlineFront.length || iconInlineEnd.length) { + if (autoWrapText) { + const textOption = Object.assign({ + text: cellValue?.toString(), + fontFamily, + fontSize, + lineHeight + }); + textOption.textBaseline = 'middle'; + const textConfig = [ + ...iconInlineFront.map(icon => dealWithRichTextIcon(icon)), + textOption, + ...iconInlineEnd.map(icon => dealWithRichTextIcon(icon)) + ]; + utilRichTextMark.setAttributes({ + width: table.getColWidth(col) - (padding[1] + padding[3]) - iconWidth, + height: 0, + textConfig + }); + maxHeight = utilRichTextMark.AABBBounds.height(); + } else { + maxHeight = 0; + lines.forEach((line: string, index: number) => { + if (index === 0 && iconInlineFront.length) { + maxHeight += Math.max(lineHeight, iconInlineFrontHeight); + } else if (index === lines.length - 1 && iconInlineEnd.length) { + maxHeight += Math.max(lineHeight, iconInlineEndHeight); + } else { + maxHeight += lineHeight; + } + }); + } + } else if (autoWrapText) { + const maxLineWidth = table.getColWidth(col) - (padding[1] + padding[3]) - iconWidth; + utilTextMark.setAttributes({ + maxLineWidth, + text: lines, + fontSize, + fontFamily + }); + maxHeight = utilTextMark.AABBBounds.height(); + } else { + // autoWrapText = false + maxHeight = lines.length * lineHeight; + } + + return Math.max(maxHeight, iconHeight) / spanRow; +} + +function dealWithRichTextIcon(iconConfig: ColumnIconOption) { + return { + width: iconConfig.width, + height: iconConfig.height + }; +} diff --git a/packages/vtable/src/themes/DEFAULT.ts b/packages/vtable/src/themes/DEFAULT.ts index 6de069cd9..45f87e1c4 100644 --- a/packages/vtable/src/themes/DEFAULT.ts +++ b/packages/vtable/src/themes/DEFAULT.ts @@ -67,7 +67,7 @@ export default { }, bodyStyle: { fontSize: 14, - bgColor: 'white', + bgColor: getBackgroundColor, hover: { // cellBorderColor: "#003fff", cellBgColor: '#CCE0FF', diff --git a/packages/vtable/src/tools/NumberMap.ts b/packages/vtable/src/tools/NumberMap.ts index 8ee7a020f..9ef03b873 100644 --- a/packages/vtable/src/tools/NumberMap.ts +++ b/packages/vtable/src/tools/NumberMap.ts @@ -127,4 +127,9 @@ export class NumberMap { } } } + clear() { + this._keys.length = 0; + this._vals = {}; + this._sorted = false; + } } diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index ced16440d..0ff541fec 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -504,6 +504,7 @@ export interface BaseTableAPI { isPivotTable: (() => boolean) & (() => boolean); _clearColRangeWidthsMap: (col?: number) => void; + _clearRowRangeHeightsMap: (row?: number) => void; //#endregion tableAPI } From b63916880f76ea1859b2580c3a0dc461b2ea2b8a Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Tue, 27 Jun 2023 10:31:48 +0800 Subject: [PATCH 03/11] feat: change row height compute method --- .../vtable/examples/auto-size/auto-height.ts | 98 +++++++++++++++++++ packages/vtable/examples/menu.ts | 4 + .../group-creater/cell-type/text-cell.ts | 5 +- .../scenegraph/group-creater/column-helper.ts | 3 +- .../scenegraph/layout/compute-row-height.ts | 2 +- packages/vtable/src/scenegraph/scenegraph.ts | 12 ++- 6 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 packages/vtable/examples/auto-size/auto-height.ts diff --git a/packages/vtable/examples/auto-size/auto-height.ts b/packages/vtable/examples/auto-size/auto-height.ts new file mode 100644 index 000000000..7d9e6206c --- /dev/null +++ b/packages/vtable/examples/auto-size/auto-height.ts @@ -0,0 +1,98 @@ +import * as VTable from '../../src'; +const ListTable = VTable.ListTable; +const Table_CONTAINER_DOM_ID = 'vTable'; + +export function createTable() { + const personsDataSource: any[] = []; + for (let i = 0; i < 10; i++) { + personsDataSource.push({ + progress: i, + id: i + 1, + name: 'name' + }); + } + const option: VTable.ListTableConstructorOptions = { + parentElement: document.getElementById(Table_CONTAINER_DOM_ID), + columns: [ + { + field: 'progress', + fieldFormat(rec) { + return `已完成已完成已完成${rec.progress}%`; + }, + caption: 'progress', + description: '这是一个标题的详细描述', + width: 150, + showSort: true, //显示VTable内置排序图标 + style: { + autoWrapText: true + } + }, + { + field: 'id', + caption: 'ID', + sort: (v1, v2, order) => { + if (order === 'desc') { + return v1 === v2 ? 0 : v1 > v2 ? -1 : 1; + } + return v1 === v2 ? 0 : v1 > v2 ? 1 : -1; + }, + width: 100 + }, + { + field: 'id', + fieldFormat(rec) { + return `手动换行\n这是这是第${rec.id}号`; + }, + caption: 'ID说明', + description: '这是一个ID详细描述', + sort: (v1, v2, order) => { + if (order === 'desc') { + return v1 === v2 ? 0 : v1 > v2 ? -1 : 1; + } + return v1 === v2 ? 0 : v1 > v2 ? 1 : -1; + }, + width: 150 + }, + { + caption: 'Name', + headerStyle: { + textAlign: 'center', + fontWeight: 'bold', + fontSize: 13, + fontFamily: 'sans-serif' + }, + field: 'name', + width: 150 + } + ], + showFrozenIcon: true, //显示VTable内置冻结列图标 + widthMode: 'standard', + allowFrozenColCount: 2, + defaultRowHeight: 50, + // theme: {}, + hover: { + // isShowTooltip: true, //当hover到未展示全的文本上时是否需要出现提示框 + // enableRowHighlight: true, //hover到的行,整行高亮 + // enableColumnHighlight: true, //hover到的行,整行高亮 + highlightMode: 'cross', + // enableSingalCellHighlight: true, //hover到的单元格高亮 + disableHeaderHover: true + }, + autoRowHeight: true + }; + + const instance = new ListTable(option); + + //设置表格数据 + instance.setRecords(personsDataSource, { + field: 'progress', + order: 'desc' + }); + + // VTable.bindDebugTool(instance.scenegraph.stage as any, { + // customGrapicKeys: ['role', '_updateTag'], + // }); + + // 只为了方便控制太调试用,不要拷贝 + (window as any).tableInstance = instance; +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index b25985df1..a85606038 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -195,6 +195,10 @@ export const menus = [ { path: 'auto-size', name: 'auto-width' + }, + { + path: 'auto-size', + name: 'auto-height' } ] }, diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts index aa2d64bca..1cef1ef26 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts @@ -54,7 +54,7 @@ export function createCellGroup( if (cellTheme?.text?.textBaseline) { textBaseline = cellTheme?.text?.textBaseline; } - const { autoRowHeight } = table.internalProps; + // const { autoRowHeight } = table.internalProps; const autoColWidth = colWidth === 'auto'; const autoWrapText = headerStyle.autoWrapText ?? table.internalProps.autoWrapText; const lineClamp = headerStyle.lineClamp; @@ -96,7 +96,8 @@ export function createCellGroup( textStr, padding as any, autoColWidth, - autoRowHeight, + // autoRowHeight, + false, autoWrapText, typeof lineClamp === 'number' ? lineClamp : undefined, // autoColWidth ? 0 : colWidth, diff --git a/packages/vtable/src/scenegraph/group-creater/column-helper.ts b/packages/vtable/src/scenegraph/group-creater/column-helper.ts index 71ba38139..0541c8380 100644 --- a/packages/vtable/src/scenegraph/group-creater/column-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/column-helper.ts @@ -90,7 +90,8 @@ export function createComplexColumn( // margin = getProp('margin', headerStyle, col, 0, table) let cellWidth = colWidth; - let cellHeight = table.internalProps.autoRowHeight ? 0 : table.getRowHeight(row); + // let cellHeight = table.internalProps.autoRowHeight ? 0 : table.getRowHeight(row); + let cellHeight = table.getRowHeight(row); const type = (table.isHeader(col, row) ? table._getHeaderLayoutMap(col, row).headerType : table.getBodyColumnType(col, row)) || 'text'; diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts index c4654add4..a9d0db99f 100644 --- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -262,7 +262,7 @@ function computeTextHeight(col: number, row: number, table: BaseTableAPI): numbe maxHeight = lines.length * lineHeight; } - return Math.max(maxHeight, iconHeight) / spanRow; + return (Math.max(maxHeight, iconHeight) + padding[0] + padding[2]) / spanRow; } function dealWithRichTextIcon(iconConfig: ColumnIconOption) { diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index 730ecad92..8388f5f80 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -33,6 +33,7 @@ import { createCellSelectBorder } from './select/create-select-border'; import { moveSelectingRangeComponentsToSelectedRangeComponents } from './select/move-select-border'; import { deleteAllSelectBorder, deleteLastSelectedRangeComponents } from './select/delete-select-border'; import { handleTextStick } from './stick-text'; +import { computeRowsHeight } from './layout/compute-row-height'; container.load(splitModule); @@ -219,6 +220,9 @@ export class Scenegraph { createSceneGraph() { this.clear = false; computeColsWidth(this.table); + if ((this.table.dataSource as any).getOriginalRecord(0)) { + computeRowsHeight(this.table); + } this.frozenColCount = this.table.rowHeaderLevelCount; this.frozenRowCount = this.table.columnHeaderLevelCount; @@ -766,10 +770,10 @@ export class Scenegraph { // 对齐auto列宽 // updateAutoColWidth(this); // 对齐autoWrapText - const { autoRowHeight } = this.table.internalProps; - if (autoRowHeight) { - updateAutoRowHeight(this); - } + // const { autoRowHeight } = this.table.internalProps; + // if (autoRowHeight) { + // updateAutoRowHeight(this); + // } this.dealWidthMode(); From ea855c823fab4ccfd05630f47538f740f1c12779 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 3 Jul 2023 19:58:37 +0800 Subject: [PATCH 04/11] feat: access computeRowsHeight into proxy --- .../vtable/examples/auto-size/auto-height.ts | 21 +++-- .../vtable/examples/auto-size/auto-width.ts | 3 +- .../vtable/src/scenegraph/graphic/text.ts | 5 +- .../group-creater/progress/proxy.ts | 86 +++++++++++-------- .../scenegraph/layout/compute-row-height.ts | 25 ++++-- .../src/scenegraph/layout/update-height.ts | 4 +- packages/vtable/src/scenegraph/scenegraph.ts | 8 +- packages/vtable/src/state/hover/col.ts | 8 +- 8 files changed, 99 insertions(+), 61 deletions(-) diff --git a/packages/vtable/examples/auto-size/auto-height.ts b/packages/vtable/examples/auto-size/auto-height.ts index 7d9e6206c..d2cb8222c 100644 --- a/packages/vtable/examples/auto-size/auto-height.ts +++ b/packages/vtable/examples/auto-size/auto-height.ts @@ -1,12 +1,13 @@ import * as VTable from '../../src'; +import { bindDebugTool } from '../../src/scenegraph/debug-tool'; const ListTable = VTable.ListTable; const Table_CONTAINER_DOM_ID = 'vTable'; export function createTable() { const personsDataSource: any[] = []; - for (let i = 0; i < 10; i++) { + for (let i = 0; i < 1000; i++) { personsDataSource.push({ - progress: i, + progress: Math.ceil(20 * Math.random()), id: i + 1, name: 'name' }); @@ -17,7 +18,12 @@ export function createTable() { { field: 'progress', fieldFormat(rec) { - return `已完成已完成已完成${rec.progress}%`; + // return `已完成已完成已完成${rec.progress}%`; + let str = ''; + for (let i = 0; i < rec.progress; i++) { + str = str + '已完成'; + } + return str + rec.progress; }, caption: 'progress', description: '这是一个标题的详细描述', @@ -25,6 +31,9 @@ export function createTable() { showSort: true, //显示VTable内置排序图标 style: { autoWrapText: true + }, + headerStyle: { + autoWrapText: true } }, { @@ -89,9 +98,9 @@ export function createTable() { order: 'desc' }); - // VTable.bindDebugTool(instance.scenegraph.stage as any, { - // customGrapicKeys: ['role', '_updateTag'], - // }); + bindDebugTool(instance.scenegraph.stage as any, { + customGrapicKeys: ['role', '_updateTag'] + }); // 只为了方便控制太调试用,不要拷贝 (window as any).tableInstance = instance; diff --git a/packages/vtable/examples/auto-size/auto-width.ts b/packages/vtable/examples/auto-size/auto-width.ts index bb952661e..1b23d31a1 100644 --- a/packages/vtable/examples/auto-size/auto-width.ts +++ b/packages/vtable/examples/auto-size/auto-width.ts @@ -1,4 +1,5 @@ import * as VTable from '../../src'; +import { bindDebugTool } from '../../src/scenegraph/debug-tool'; const ListTable = VTable.ListTable; const Table_CONTAINER_DOM_ID = 'vTable'; @@ -221,7 +222,7 @@ export function createTable() { const instance = new ListTable(option); - VTable.bindDebugTool(instance.scenegraph.stage as any, { + bindDebugTool(instance.scenegraph.stage as any, { customGrapicKeys: ['role', '_updateTag'] }); diff --git a/packages/vtable/src/scenegraph/graphic/text.ts b/packages/vtable/src/scenegraph/graphic/text.ts index c98181c6f..c89b2d76c 100644 --- a/packages/vtable/src/scenegraph/graphic/text.ts +++ b/packages/vtable/src/scenegraph/graphic/text.ts @@ -34,7 +34,6 @@ export class WrapText extends Text { // const textMeasure = graphicUtil.textMeasure; let width: number; let str: string; - const buf = 2; const attribute = this.attribute; const { maxLineWidth = textTheme.maxLineWidth, @@ -44,7 +43,7 @@ export class WrapText extends Text { fontSize = textTheme.fontSize, fontFamily = textTheme.fontFamily, stroke = textTheme.stroke, - lineHeight = attribute.lineHeight ?? (attribute.fontSize ?? textTheme.lineHeight ?? textTheme.fontSize) + buf, + lineHeight = attribute.lineHeight ?? attribute.fontSize ?? textTheme.fontSize, lineWidth = textTheme.lineWidth } = attribute; @@ -108,7 +107,7 @@ export class WrapText extends Text { textAlign = textTheme.textAlign, textBaseline = textTheme.textBaseline, fontSize = textTheme.fontSize, - lineHeight = this.attribute.lineHeight ?? this.attribute.fontSize ?? textTheme.lineHeight ?? textTheme.fontSize, + lineHeight = this.attribute.lineHeight ?? this.attribute.fontSize ?? textTheme.fontSize, ellipsis = textTheme.ellipsis, maxLineWidth, stroke = textTheme.stroke, diff --git a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts index 8a00da1ea..813cc230e 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts @@ -1,6 +1,7 @@ import type { BaseTableAPI } from '../../../ts-types/base-table'; import type { Group } from '../../graphic/group'; import type { WrapText } from '../../graphic/text'; +import { computeRowsHeight } from '../../layout/compute-row-height'; import { updateCellHeightForColumn } from '../../layout/update-height'; import { emptyGroup } from '../../utils/empty-group'; import { getProp } from '../../utils/get-prop'; @@ -13,7 +14,7 @@ export class SceneProxy { mode: 'column' | 'row' | 'pivot' = 'column'; currentRow = 0; totalRow: number; - rowLimit = 1000; + rowLimit = 20; yLimitTop: number; // y > yLimitTop动态更新,否则直接修改xy yLimitBottom: number; // y < yLimitBottom动态更新,否则直接修改xy // bottomOffset: number; @@ -89,6 +90,9 @@ export class SceneProxy { ) { this.setParams(); + // compute row height in first screen + computeRowsHeight(table, this.table.columnHeaderLevelCount, Math.min(this.firstScreenRowLimit, table.rowCount - 1)); + // 生成首屏单元格 // rowHeader createColGroup( @@ -177,6 +181,8 @@ export class SceneProxy { createRowCellGroup(onceCount: number) { const endRow = Math.min(this.totalRow, this.currentRow + onceCount); + // compute rows height + computeRowsHeight(this.table, this.currentRow + 1, endRow); let maxHeight = 0; for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { const colGroup = this.table.scenegraph.getColGroup(col); @@ -196,15 +202,15 @@ export class SceneProxy { } this.table.scenegraph.bodyGroup.setAttribute('height', maxHeight); - if (this.table.internalProps.autoRowHeight) { - updateAutoRow( - this.bodyLeftCol, // colStart - this.bodyRightCol, // colEnd - this.currentRow + 1, // rowStart - endRow, // rowEnd - this.table - ); - } + // if (this.table.internalProps.autoRowHeight) { + // updateAutoRow( + // this.bodyLeftCol, // colStart + // this.bodyRightCol, // colEnd + // this.currentRow + 1, // rowStart + // endRow, // rowEnd + // this.table + // ); + // } this.currentRow = endRow; this.rowEnd = endRow; this.rowUpdatePos = this.rowEnd; @@ -316,6 +322,9 @@ export class SceneProxy { // 更新同步范围 const syncTopRow = Math.max(this.bodyTopRow, screenTopRow - this.screenRowCount * 2); const syncBottomRow = Math.min(this.bodyBottomRow, screenTopRow + this.screenRowCount * 3); + if (this.table.internalProps.autoRowHeight) { + computeRowsHeight(this.table, syncTopRow, syncBottomRow); + } for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { for (let row = syncTopRow; row <= syncBottomRow; row++) { // const cellGroup = this.table.scenegraph.getCell(col, row); @@ -356,7 +365,7 @@ export class SceneProxy { const distStartRowY = this.table.getRowsHeight(this.bodyTopRow, distStartRow - 1); for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { const colGroup = this.table.scenegraph.getColGroup(col); - colGroup.forEachChildren((cellGroup: Group, index) => { + colGroup?.forEachChildren((cellGroup: Group, index) => { // 这里使用colGroup变量而不是for this.rowStart to this.rowEndthis.rowEnd是因为在更新内可能出现row号码重复的情况 this.updateCellGroupPosition( cellGroup, @@ -379,6 +388,9 @@ export class SceneProxy { syncBottomRow = Math.min(this.bodyBottomRow, screenTopRow + this.screenRowCount * 3); } console.log('更新同步范围', syncTopRow, syncBottomRow); + if (this.table.internalProps.autoRowHeight) { + computeRowsHeight(this.table, syncTopRow, syncBottomRow); + } for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { for (let row = syncTopRow; row <= syncBottomRow; row++) { // const cellGroup = this.table.scenegraph.getCell(col, row); @@ -426,6 +438,9 @@ export class SceneProxy { updateCellGroups(count: number) { const distRow = Math.min(this.bodyBottomRow, this.rowUpdatePos + count); console.log('updateCellGroups', this.rowUpdatePos, distRow); + if (this.table.internalProps.autoRowHeight) { + computeRowsHeight(this.table, this.rowUpdatePos, distRow); + } for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { for (let row = this.rowUpdatePos; row <= distRow; row++) { // const cellGroup = this.table.scenegraph.getCell(col, row); @@ -498,6 +513,9 @@ export class SceneProxy { syncBottomRow = Math.min(this.bodyBottomRow, this.screenTopRow + this.screenRowCount * 3); } console.log('sort更新同步范围', syncTopRow, syncBottomRow); + if (this.table.internalProps.autoRowHeight) { + computeRowsHeight(this.table, syncTopRow, syncBottomRow); + } for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { for (let row = syncTopRow; row <= syncBottomRow; row++) { // const cellGroup = this.table.scenegraph.getCell(col, row); @@ -570,30 +588,30 @@ function updateAutoRow( table: BaseTableAPI, direction: 'up' | 'down' = 'up' ) { - // 获取行高 - for (let row = rowStart; row <= rowEnd; row++) { - let maxRowHeight = 0; - for (let col = colStart; col <= colEnd; col++) { - const cellGroup = table.scenegraph.getCell(col, row); - if (!cellGroup.row) { - continue; - } - // const contentHeight = cellGroup.getContentHeight(); - const text = (cellGroup.getChildByName('text') as WrapText) || cellGroup.getChildByName('content'); - const headerStyle = table._getCellStyle(col, row); - const padding = getQuadProps(getProp('padding', headerStyle, col, row, table)); - const height = text.AABBBounds.height() + (padding[0] + padding[2]); - maxRowHeight = Math.max(maxRowHeight, height); - (cellGroup as any).needUpdateForAutoRowHeight = false; - } - // updateRowHeight(table.scenegraph, row, table.getRowHeight(row) - maxRowHeight); - for (let col = colStart; col <= colEnd; col++) { - const cellGroup = table.scenegraph.getCell(col, row); - updateCellHeightForColumn(table.scenegraph, cellGroup, col, row, maxRowHeight, 0, false); - } + // // 获取行高 + // for (let row = rowStart; row <= rowEnd; row++) { + // let maxRowHeight = 0; + // for (let col = colStart; col <= colEnd; col++) { + // const cellGroup = table.scenegraph.highPerformanceGetCell(col, row); + // if (!cellGroup.row) { + // continue; + // } + // // const contentHeight = cellGroup.getContentHeight(); + // const text = (cellGroup.getChildByName('text') as WrapText) || cellGroup.getChildByName('content'); + // const headerStyle = table._getCellStyle(col, row); + // const padding = getQuadProps(getProp('padding', headerStyle, col, row, table)); + // const height = text.AABBBounds.height() + (padding[0] + padding[2]); + // maxRowHeight = Math.max(maxRowHeight, height); + // (cellGroup as any).needUpdateForAutoRowHeight = false; + // } + // // updateRowHeight(table.scenegraph, row, table.getRowHeight(row) - maxRowHeight); + // for (let col = colStart; col <= colEnd; col++) { + // const cellGroup = table.scenegraph.highPerformanceGetCell(col, row); + // updateCellHeightForColumn(table.scenegraph, cellGroup, col, row, maxRowHeight, 0, false); + // } - table.setRowHeight(row, maxRowHeight, true); - } + // table.setRowHeight(row, maxRowHeight, true); + // } // 更新y位置 if (direction === 'up') { diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts index 3c98bbe34..1801abe2a 100644 --- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -10,29 +10,34 @@ import { getProp } from '../utils/get-prop'; import { getQuadProps } from '../utils/padding'; import { getCellRect } from './compute-col-width'; -const utilTextMark = new WrapText({}); +const utilTextMark = new WrapText({ + autoWrapText: true +}); const utilRichTextMark = new RichText({ width: 0, height: 0, textConfig: [] }); -export function computeRowsHeight(table: BaseTableAPI): void { +export function computeRowsHeight(table: BaseTableAPI, rowStart?: number, rowEnd?: number): void { if (!table.internalProps.autoRowHeight) { // autoRowHeight false use default height return; } + rowStart = rowStart ?? 0; + rowEnd = rowEnd ?? table.rowCount - 1; const time = typeof window !== 'undefined' ? window.performance.now() : 0; - table._clearRowRangeHeightsMap(); - // table.rowHeightsMap.clear(); - table.internalProps._rowHeightsMap.clear(); // compute header row in column header row - for (let row = 0; row < table.columnHeaderLevelCount; row++) { + for (let row = rowStart; row < table.columnHeaderLevelCount; row++) { const height = computeRowHeight(row, 0, table.colCount - 1, table); table.setRowHeight(row, height); } + if (rowEnd < table.columnHeaderLevelCount) { + return; + } + // compute body row if ( !table.internalProps.transpose && @@ -50,18 +55,20 @@ export function computeRowsHeight(table: BaseTableAPI): void { ) { // check fixed style and no wrap situation, just compute 0-table.rowHeaderLevelCount column(the column after row header) in ervey row // in traspose table and row indicator pivot table - for (let row = table.columnHeaderLevelCount; row < table.rowCount; row++) { + for (let row = Math.max(rowStart, table.columnHeaderLevelCount); row < rowEnd; row++) { + table._clearRowRangeHeightsMap(row); const height = computeRowHeight(row, 0, table.rowHeaderLevelCount, table); table.setRowHeight(row, height); } } else { - for (let row = table.columnHeaderLevelCount; row < table.rowCount; row++) { + for (let row = Math.max(rowStart, table.columnHeaderLevelCount); row < rowEnd; row++) { + table._clearRowRangeHeightsMap(row); const height = computeRowHeight(row, 0, table.colCount - 1, table); table.setRowHeight(row, height); } } - console.log('computeColsWidth time:', (typeof window !== 'undefined' ? window.performance.now() : 0) - time); + console.log('computeRowsHeight time:', (typeof window !== 'undefined' ? window.performance.now() : 0) - time); } export function computeRowHeight(row: number, startCol: number, endCol: number, table: BaseTableAPI): number { diff --git a/packages/vtable/src/scenegraph/layout/update-height.ts b/packages/vtable/src/scenegraph/layout/update-height.ts index 03dab7f15..a904f37fd 100644 --- a/packages/vtable/src/scenegraph/layout/update-height.ts +++ b/packages/vtable/src/scenegraph/layout/update-height.ts @@ -10,6 +10,7 @@ import { updateCellContentHeight } from '../utils/text-icon-layout'; import type { IProgressbarColumnBodyDefine } from '../../ts-types/list-table/define/progressbar-define'; import { dealWithCustom } from '../component/custom'; import { updateImageCellContentWhileResize } from '../group-creater/cell-type/image-cell'; +import { getStyleTheme } from '../../core/tableHelper'; export function updateRowHeight(scene: Scenegraph, row: number, detaY: number) { for (let col = 0; col < scene.table.colCount; col++) { @@ -209,7 +210,8 @@ export function updateCellHeight( cell.attribute.width, cell.attribute.height, padding, - scene.table + scene.table, + getStyleTheme(headerStyle, scene.table, col, row, getProp).theme ); } else if (type === 'image' || type === 'video') { updateImageCellContentWhileResize(cell, col, row, scene.table); diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index 0d7481ba4..22ee73247 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -285,9 +285,11 @@ export class Scenegraph { createSceneGraph() { this.clear = false; computeColsWidth(this.table); - if ((this.table.dataSource as any).getOriginalRecord(0)) { - computeRowsHeight(this.table); - } + // compute the column header cell height + // table.rowHeightsMap.clear(); + this.table._clearRowRangeHeightsMap(); + this.table.internalProps._rowHeightsMap.clear(); + computeRowsHeight(this.table, 0, this.table.columnHeaderLevelCount - 1); this.frozenColCount = this.table.rowHeaderLevelCount; this.frozenRowCount = this.table.columnHeaderLevelCount; diff --git a/packages/vtable/src/state/hover/col.ts b/packages/vtable/src/state/hover/col.ts index d5b9d4c7b..174fd0403 100644 --- a/packages/vtable/src/state/hover/col.ts +++ b/packages/vtable/src/state/hover/col.ts @@ -17,8 +17,8 @@ export function clearColHover( updateCell(scenegraph, col, row); } // 更新body - const cellGroup = scenegraph.getCell(col, table.columnHeaderLevelCount); - cellGroup?.parent.addUpdateBoundTag(); + const cellGroup = scenegraph.getColGroup(col); + cellGroup?.addUpdateBoundTag(); return true; } @@ -39,8 +39,8 @@ export function updateColHover( updateCell(scenegraph, col, row); } // 更新body - const cellGroup = scenegraph.getCell(col, table.columnHeaderLevelCount); - cellGroup?.parent.addUpdateBoundTag(); + const cellGroup = scenegraph.getColGroup(col); + cellGroup?.addUpdateBoundTag(); return true; } From e9d11f18d1e2bbed9b0c8da6512be3c4c60e5fbe Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 3 Jul 2023 20:14:29 +0800 Subject: [PATCH 05/11] fix: fix merge problems --- packages/vtable/src/scenegraph/group-creater/cell-helper.ts | 2 ++ .../vtable/src/scenegraph/group-creater/cell-type/text-cell.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index 802d4a30d..6fcae24ab 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -136,6 +136,7 @@ export function createCell( textAlign, textBaseline, mayHaveIcon, + customElementsGroup, renderDefault, cellTheme ); @@ -216,6 +217,7 @@ export function createCell( textAlign, textBaseline, false, + null, true, cellTheme ); diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts index 2a16e7329..4df107360 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts @@ -40,6 +40,7 @@ export function createCellGroup( textAlign: CanvasTextAlign, textBaseline: CanvasTextBaseline, mayHaveIcon: boolean, + customElementsGroup: Group, renderDefault: boolean, cellTheme: IThemeSpec ): Group { From 1eaed919999069ef7a81c27dbf280b46ce766168 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 3 Jul 2023 20:55:26 +0800 Subject: [PATCH 06/11] fix: fix tableGroup position problem --- packages/vtable/src/scenegraph/style/frame-border.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vtable/src/scenegraph/style/frame-border.ts b/packages/vtable/src/scenegraph/style/frame-border.ts index 93d6db106..970930974 100644 --- a/packages/vtable/src/scenegraph/style/frame-border.ts +++ b/packages/vtable/src/scenegraph/style/frame-border.ts @@ -97,8 +97,8 @@ export function createFrameBorder( const deltaX = (rectAttributes.shadowBlur ?? 0) + (borderLeft + borderRight) / 2; const deltaY = (rectAttributes.shadowBlur ?? 0) + (borderTop + borderBottom) / 2; - groupAttributes.x = group.attribute.x + deltaX; - groupAttributes.y = group.attribute.y + deltaY; + groupAttributes.x = deltaX; + groupAttributes.y = deltaY; // 宽度高度在tableNoFrameWidth&tableNoFrameHeight中处理 // groupAttributes.width = group.attribute.width - deltaX - deltaX; // groupAttributes.height = group.attribute.height - deltaY - deltaY; From 40961e214cd39d78fcc4841fc42f5ba3c1881a77 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 6 Jul 2023 17:49:25 +0800 Subject: [PATCH 07/11] feat: refactor progress proxy to enhance performance --- .../vtable/examples/list/list-transpose.ts | 82 +++ packages/vtable/examples/list/list.ts | 2 +- .../scenegraph/group-creater/cell-helper.ts | 8 +- .../group-creater/progress/proxy.ts | 604 +++++++++--------- .../progress/update-position/dynamic-set-x.ts | 184 ++++++ .../progress/update-position/dynamic-set-y.ts | 198 ++++++ .../update-position/update-auto-row.ts | 74 +++ .../scenegraph/layout/compute-col-width.ts | 26 +- .../scenegraph/layout/compute-row-height.ts | 9 + packages/vtable/src/scenegraph/scenegraph.ts | 128 ++-- packages/vtable/src/ts-types/base-table.ts | 3 + 11 files changed, 918 insertions(+), 400 deletions(-) create mode 100644 packages/vtable/examples/list/list-transpose.ts create mode 100644 packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-x.ts create mode 100644 packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts create mode 100644 packages/vtable/src/scenegraph/group-creater/progress/update-position/update-auto-row.ts diff --git a/packages/vtable/examples/list/list-transpose.ts b/packages/vtable/examples/list/list-transpose.ts new file mode 100644 index 000000000..3f4e32d0a --- /dev/null +++ b/packages/vtable/examples/list/list-transpose.ts @@ -0,0 +1,82 @@ +import * as VTable from '../../src'; +const Table_CONTAINER_DOM_ID = 'vTable'; +const generatePersons = count => { + return Array.from(new Array(count)).map((_, i) => ({ + id: i + 1, + email1: `${i + 1}@xxx.com`, + name: `小明${i + 1}`, + lastName: '王', + date1: '2022年9月1日', + tel: '000-0000-0000', + sex: i % 2 === 0 ? 'boy' : 'girl', + work: i % 2 === 0 ? 'back-end engineer' : 'front-end engineer', + city: 'beijing' + })); +}; + +export function createTable() { + const records = generatePersons(1000000); + const columns: VTable.ColumnsDefine = [ + { + field: 'id', + caption: 'ID', + width: 120, + sort: true + }, + { + field: 'email1', + caption: 'email', + width: 200, + sort: true + }, + { + caption: 'full name', + columns: [ + { + field: 'name', + caption: 'First Name', + width: 200 + }, + { + field: 'name', + caption: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + caption: 'birthday', + width: 200 + }, + { + field: 'sex', + caption: 'sex', + width: 100 + }, + { + field: 'tel', + caption: 'telephone', + width: 150 + }, + { + field: 'work', + caption: 'job', + width: 200 + }, + { + field: 'city', + caption: 'city', + width: 150 + } + ]; + const option = { + parentElement: document.getElementById(Table_CONTAINER_DOM_ID), + records, + columns, + transpose: true, + defaultColWidth: 200 + }; + const tableInstance = new VTable.ListTable(option); + (window as any).tableInstance = tableInstance; +} diff --git a/packages/vtable/examples/list/list.ts b/packages/vtable/examples/list/list.ts index 6fdb54493..99eef7102 100644 --- a/packages/vtable/examples/list/list.ts +++ b/packages/vtable/examples/list/list.ts @@ -76,5 +76,5 @@ export function createTable() { columns }; const tableInstance = new VTable.ListTable(option); - window.tableInstance = tableInstance; + (window as any).tableInstance = tableInstance; } diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index 6fcae24ab..3b71ad0dd 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -256,7 +256,8 @@ export function createCell( } export function updateCell(col: number, row: number, table: BaseTableAPI, addNew?: boolean) { - const oldCellGroup = table.scenegraph.getCell(col, row, true); + // const oldCellGroup = table.scenegraph.getCell(col, row, true); + const oldCellGroup = table.scenegraph.highPerformanceGetCell(col, row, true); const type = table.isHeader(col, row) ? table._getHeaderLayoutMap(col, row).headerType @@ -436,6 +437,11 @@ function updateCellContent( if (!addNew) { oldCellGroup.parent.insertAfter(newCellGroup, oldCellGroup); oldCellGroup.parent.removeChild(oldCellGroup); + + // update cache + if (table.scenegraph?.proxy.cellCache.get(col)) { + table.scenegraph?.proxy.cellCache.set(col, newCellGroup); + } } return newCellGroup; } diff --git a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts index 813cc230e..c24f2bfb5 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts @@ -1,41 +1,51 @@ import type { BaseTableAPI } from '../../../ts-types/base-table'; -import type { Group } from '../../graphic/group'; -import type { WrapText } from '../../graphic/text'; +import { Group } from '../../graphic/group'; +import { computeColsWidth } from '../../layout/compute-col-width'; import { computeRowsHeight } from '../../layout/compute-row-height'; -import { updateCellHeightForColumn } from '../../layout/update-height'; import { emptyGroup } from '../../utils/empty-group'; -import { getProp } from '../../utils/get-prop'; -import { getQuadProps } from '../../utils/padding'; import { createColGroup } from '../column'; import { createComplexColumn } from '../column-helper'; +import { dynamicSetX } from './update-position/dynamic-set-x'; +import { dynamicSetY } from './update-position/dynamic-set-y'; +import { updateAutoRow } from './update-position/update-auto-row'; export class SceneProxy { table: BaseTableAPI; mode: 'column' | 'row' | 'pivot' = 'column'; - currentRow = 0; - totalRow: number; - rowLimit = 20; + + rowLimit = 1000; + currentRow = 0; // 目前渐进生成的row number + totalRow: number; // 渐进完成最后一行的row number yLimitTop: number; // y > yLimitTop动态更新,否则直接修改xy yLimitBottom: number; // y < yLimitBottom动态更新,否则直接修改xy - // bottomOffset: number; - // scroll - accurateY = 0; - rowStart = 0; - rowEnd = 0; - referenceRow = 0; - - bodyTopRow: number; - bodyBottomRow: number; - bodyLeftCol: number; - bodyRightCol: number; - screenRowCount: number; - firstScreenRowLimit: number; - taskRowCount: number; - rowUpdatePos: number; - rowUpdateDirection: 'up' | 'down'; - screenTopRow: number = 0; - screenTopRowDeltaY: number; - y: number; + rowStart = 0; // 当前维护的部分第一行的row number + rowEnd = 0; // 当前维护的部分最后一行的row number + referenceRow = 0; // 当前维护的部分中间一行的row number,认为referenceRow对应当前屏幕显示范围的第一行 + bodyTopRow: number; // table body部分的第一行row number + bodyBottomRow: number; // table body部分的最后一行row number + screenRowCount: number; // 预计屏幕范围内显示的row count + firstScreenRowLimit: number; // 首屏同步加载部分最后一行的row number + taskRowCount: number; // 一次任务生成/更新的row count + rowUpdatePos: number; // 异步任务目前更新到的行的row number + rowUpdateDirection: 'up' | 'down'; // 当前行更新的方向 + screenTopRow: number = 0; // 当前屏幕范围内显示的第一行的row number + + colLimit = 1000; + bodyLeftCol: number; // table body部分的第一列col number + bodyRightCol: number; // table body部分的最后一列col number + totalCol: number; // 渐进完成最后一列的col number + colStart: number; // 当前维护的部分第一列的col number + colEnd: number; // 当前维护的部分最后一列的col number + taskColCount: number; // 一次任务生成/更新的col count + xLimitLeft: number; // x > xLimitLeft动态更新,否则直接修改xy + xLimitRight: number; // x < xLimitRight动态更新,否则直接修改xy + screenColCount: number; // 预计屏幕范围内显示的col count + firstScreenColLimit: number; // 首屏同步加载部分最后一列的col number + colUpdatePos: number; // 异步任务目前更新到的列的col number + currentCol: number; // 目前渐进生成的col number + referenceCol: number; // 当前维护的部分中间一列的col number,认为referenceCol对应当前屏幕显示范围的第一列 + screenLeftCol: number = 0; // 当前屏幕范围内显示的第一列的col number + colUpdateDirection: 'left' | 'right'; // 当前列更新方向 cellCache: Map = new Map(); // 单元格位置快速查找缓存 @@ -52,18 +62,44 @@ export class SceneProxy { } } - setParams() { + setParamsForColumn() { + this.bodyLeftCol = this.table.rowHeaderLevelCount; + this.bodyRightCol = this.table.colCount - 1; + + // compute the column info about progress creation + const totalActualBodyColCount = Math.min(this.colLimit, this.bodyRightCol - this.bodyLeftCol + 1); + this.totalCol = this.bodyLeftCol + totalActualBodyColCount - 1; // 目标渐进完成的col + this.colStart = this.bodyLeftCol; + const defaultColWidth = this.table.defaultColWidth; + this.taskColCount = Math.ceil(this.table.tableNoFrameWidth / defaultColWidth) * 1; + + // 确定动态更新限制 + const totalBodyWidth = defaultColWidth * totalActualBodyColCount; + const totalWidth = defaultColWidth * (this.bodyRightCol - this.bodyLeftCol + 1); + this.xLimitLeft = totalBodyWidth / 2; + this.xLimitRight = totalWidth - totalBodyWidth / 2; + + // 确定首屏高度范围 + const widthLimit = this.table.tableNoFrameWidth * 5; + this.screenColCount = Math.ceil(this.table.tableNoFrameWidth / defaultColWidth); + this.firstScreenColLimit = this.bodyLeftCol + Math.ceil(widthLimit / defaultColWidth); + // this.firstScreenRowLimit = this.bodyBottomRow; + + this.colUpdatePos = this.bodyRightCol; + } + + setParamsForRow() { this.bodyTopRow = this.table.columnHeaderLevelCount; this.bodyBottomRow = this.table.rowCount - 1; this.bodyLeftCol = 0; this.bodyRightCol = this.table.colCount - 1; // 计算渐进加载数量 - const totalActualBodyRowCount = Math.min(this.rowLimit, this.bodyBottomRow - this.bodyTopRow + 1); + const totalActualBodyRowCount = Math.min(this.rowLimit, this.bodyBottomRow - this.bodyTopRow + 1); // 渐进加载总row数量 this.totalRow = this.bodyTopRow + totalActualBodyRowCount - 1; // 目标渐进完成的row this.rowStart = this.bodyTopRow; const defaultRowHeight = this.table.defaultRowHeight; - this.taskRowCount = Math.ceil(this.table.tableNoFrameHeight / defaultRowHeight) * 5; + this.taskRowCount = Math.ceil(this.table.tableNoFrameHeight / defaultRowHeight) * 1; // 确定动态更新限制 const totalBodyHeight = defaultRowHeight * totalActualBodyRowCount; @@ -81,6 +117,108 @@ export class SceneProxy { this.rowUpdatePos = this.bodyBottomRow; } + async createGroupForFirstScreen( + cornerHeaderGroup: Group, + colHeaderGroup: Group, + rowHeaderGroup: Group, + bodyGroup: Group, + xOrigin: number, + yOrigin: number + ) { + // compute parameters + this.setParamsForRow(); + this.setParamsForColumn(); + + // compute colums width in first screen + this.table.internalProps._colWidthsMap.clear(); + this.table._clearColRangeWidthsMap(); + computeColsWidth(this.table, 0, Math.min(this.firstScreenColLimit, this.table.colCount - 1)); + + // compute rows height in first screen + this.table.internalProps._rowHeightsMap.clear(); + this.table._clearRowRangeHeightsMap(); + computeRowsHeight(this.table, 0, Math.min(this.firstScreenRowLimit, this.table.rowCount - 1)); + + // create cornerHeaderGroup + createColGroup( + cornerHeaderGroup, + xOrigin, + yOrigin, + 0, // colStart + this.table.rowHeaderLevelCount - 1, // colEnd + 0, // rowStart + this.table.columnHeaderLevelCount - 1, // rowEnd + 'cornerHeader', // CellType + this.table + ); + + // create colHeaderGroup + createColGroup( + colHeaderGroup, + xOrigin, + yOrigin, + this.table.rowHeaderLevelCount, // colStart + Math.min(this.firstScreenColLimit, this.table.colCount - 1), // colEnd + 0, // rowStart + this.table.columnHeaderLevelCount - 1, // rowEnd + 'columnHeader', // isHeader + this.table + ); + + // create rowHeaderGroup + createColGroup( + rowHeaderGroup, + xOrigin, + yOrigin, + 0, // colStart + this.table.rowHeaderLevelCount - 1, // colEnd + this.table.columnHeaderLevelCount, // rowStart + Math.min(this.firstScreenRowLimit, this.table.rowCount - 1), // rowEnd + 'rowHeader', // isHeader + this.table + ); + + // create bodyGroup + createColGroup( + bodyGroup, + xOrigin, + yOrigin, + this.table.rowHeaderLevelCount, // colStart + Math.min(this.firstScreenColLimit, this.table.colCount - 1), // colEnd + this.table.columnHeaderLevelCount, // rowStart + Math.min(this.firstScreenRowLimit, this.table.rowCount - 1), // rowEnd + 'body', // isHeader + this.table + ); + + // update progress information + if (!bodyGroup.firstChild) { + // 无数据 + this.currentRow = this.totalRow; + this.rowEnd = this.currentRow; + this.rowUpdatePos = this.rowEnd; + this.referenceRow = Math.floor((this.rowEnd - this.rowStart) / 2); + + this.currentCol = this.totalCol; + this.colEnd = this.currentCol; + this.colUpdatePos = this.colEnd; + this.referenceCol = Math.floor((this.colEnd - this.colStart) / 2); + } else { + this.currentRow = (bodyGroup.firstChild as Group)?.rowNumber ?? this.totalRow; + this.rowEnd = this.currentRow; + this.rowUpdatePos = this.rowEnd; + this.referenceRow = Math.floor((this.rowEnd - this.rowStart) / 2); + + this.currentCol = (bodyGroup.lastChild as Group)?.col ?? this.totalCol; + this.colEnd = this.currentCol; + this.colUpdatePos = this.colEnd; + this.referenceCol = Math.floor((this.colEnd - this.colStart) / 2); + + // 开始异步任务 + await this.progress(); + } + } + async createColGroupForFirstScreen( rowHeaderGroup: Group, bodyGroup: Group, @@ -88,7 +226,8 @@ export class SceneProxy { yOrigin: number, table: BaseTableAPI ) { - this.setParams(); + this.setParamsForRow(); + this.setParamsForColumn(); // compute row height in first screen computeRowsHeight(table, this.table.columnHeaderLevelCount, Math.min(this.firstScreenRowLimit, table.rowCount - 1)); @@ -153,19 +292,24 @@ export class SceneProxy { // } async progress() { return new Promise((resolve, reject) => { - setTimeout(() => { - if (this.rowUpdatePos < this.rowEnd) { + setTimeout(async () => { + if (this.colUpdatePos < this.colEnd) { + await this.updateColCellGroupsAsync(); + await this.progress(); + } else if (this.rowUpdatePos < this.rowEnd) { // console.log('progress rowUpdatePos', this.rowUpdatePos); // 先更新 - this.updateCellGroupsAsync(); - this.progress(); + await this.updateRowCellGroupsAsync(); + await this.progress(); + } else if (this.currentCol < this.totalCol) { + await this.createCol(); + await this.progress(); } else if (this.currentRow < this.totalRow) { // console.log('progress currentRow', this.currentRow); // 先更新没有需要更新的节点,在生成新节点 - this.createRow(); - this.progress(); + await this.createRow(); + await this.progress(); } - resolve(); }, 0); }); @@ -179,10 +323,20 @@ export class SceneProxy { this.createRowCellGroup(this.taskRowCount); } + async createCol() { + if (!this.taskColCount) { + return; + } + console.log('createCol', this.currentCol, this.currentCol + this.taskColCount); + this.createColGroup(this.taskRowCount); + } + createRowCellGroup(onceCount: number) { const endRow = Math.min(this.totalRow, this.currentRow + onceCount); // compute rows height computeRowsHeight(this.table, this.currentRow + 1, endRow); + + // create row cellGroup let maxHeight = 0; for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { const colGroup = this.table.scenegraph.getColGroup(col); @@ -202,15 +356,6 @@ export class SceneProxy { } this.table.scenegraph.bodyGroup.setAttribute('height', maxHeight); - // if (this.table.internalProps.autoRowHeight) { - // updateAutoRow( - // this.bodyLeftCol, // colStart - // this.bodyRightCol, // colEnd - // this.currentRow + 1, // rowStart - // endRow, // rowEnd - // this.table - // ); - // } this.currentRow = endRow; this.rowEnd = endRow; this.rowUpdatePos = this.rowEnd; @@ -221,6 +366,43 @@ export class SceneProxy { this.table.scenegraph.updateBorderSizeAndPosition(); } + createColGroup(onceCount: number) { + // compute rows height + const endCol = Math.min(this.totalCol, this.currentCol + onceCount); + computeColsWidth(this.table, this.currentCol + 1, endCol); + + // create colGroup + const lastColumnGroup = ( + this.table.scenegraph.bodyGroup.lastChild instanceof Group + ? this.table.scenegraph.bodyGroup.lastChild + : this.table.scenegraph.bodyGroup.lastChild._prev + ) as Group; + const xOrigin = lastColumnGroup.attribute.x + lastColumnGroup.attribute.width; + const yOrigin = lastColumnGroup.attribute.y; + // create bodyGroup + createColGroup( + this.table.scenegraph.bodyGroup, + xOrigin, + yOrigin, + this.currentCol + 1, // colStart + endCol, // colEnd + this.rowStart, // rowStart + this.rowEnd, // rowEnd + 'body', // isHeader + this.table + ); + + this.currentCol = endCol; + this.colEnd = endCol; + this.colUpdatePos = this.colEnd; + this.referenceCol = Math.floor((endCol - this.colStart) / 2); + console.log('async', this.referenceCol, this.colStart, this.colEnd); + + // update container group size and border + this.table.scenegraph.updateContainer(); + this.table.scenegraph.updateBorderSizeAndPosition(); + } + async setY(y: number) { if (y < this.yLimitTop && this.rowStart === this.bodyTopRow) { // 执行真实body group坐标修改 @@ -234,210 +416,37 @@ export class SceneProxy { } } - async dynamicSetY(y: number) { - // 计算变动row range - // const screenTopRow = this.table.getRowAt(y).row; - const screenTop = (this.table as any).getTargetRowAt(y + this.table.scenegraph.colHeaderGroup.attribute.height); - if (!screenTop) { - return; - } - const screenTopRow = screenTop.row; - this.y = y; - this.screenTopRow = screenTopRow; - // this.screenTopRowDeltaY = y - this.table.getRowsHeight(this.bodyTopRow, screenTopRow - 1); - const deltaRow = screenTopRow - this.referenceRow; - if (deltaRow > 0) { - // 向下滚动,顶部cell group移到底部 - this.moveCell(deltaRow, 'up', screenTopRow); - this.updateBody(y); - // if (this.rowEnd === this.table.scenegraph.proxy.bodyBottomRow) { - // const totalHeight = this.table.getAllRowsHeight(); - // const top = totalHeight - this.table.scenegraph.height; - // this.updateBody(top); - // } else { - // this.updateBody(y); - // } - } else if (deltaRow < 0) { - // 向上滚动,底部cell group移到顶部 - this.moveCell(-deltaRow, 'down', screenTopRow); - this.updateBody(y); - // if (this.rowStart === this.bodyTopRow) { - // this.updateBody(0); - // } else { - // this.updateBody(y); - // } + async setX(x: number) { + if (x < this.xLimitLeft && this.colStart === this.bodyLeftCol) { + // 执行真实body group坐标修改 + this.table.scenegraph.setBodyAndColHeaderX(-x); + } else if (x > this.xLimitRight && this.colEnd === this.bodyRightCol) { + // 执行真实body group坐标修改 + this.table.scenegraph.setBodyAndColHeaderX(-x); } else { - // 不改变row,更新body group范围 - this.updateBody(y); + // 执行动态更新节点 + this.dynamicSetX(x); } + } - this.table.scenegraph.updateNextFrame(); + async dynamicSetY(y: number) { + dynamicSetY(y, this); + } + async dynamicSetX(x: number) { + dynamicSetX(x, this); } updateBody(y: number) { this.table.scenegraph.setBodyAndRowHeaderY(-y); } - async moveCell(count: number, direction: 'up' | 'down', screenTopRow: number) { - // 限制count范围 - if (direction === 'up' && this.rowEnd + count > this.bodyBottomRow) { - count = this.bodyBottomRow - this.rowEnd; - } else if (direction === 'down' && this.rowStart - count < this.bodyTopRow) { - count = this.rowStart - this.bodyTopRow; - } - - // 两种更新模式 - // 1. count < rowEnd - rowStart:从顶/底部移动count数量的单元格到底/顶部 - // 2. count >= rowEnd - rowStart:整体移动到目标位置 - if (count < this.rowEnd - this.rowStart) { - // 计算更新区域 - const startRow = direction === 'up' ? this.rowStart : this.rowEnd - count + 1; - const endRow = direction === 'up' ? this.rowStart + count - 1 : this.rowEnd; - // console.log('move', startRow, endRow, direction); - for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { - const colGroup = this.table.scenegraph.getColGroup(col); - for (let row = startRow; row <= endRow; row++) { - if (direction === 'up') { - const cellGroup = colGroup.firstChild as Group; - this.updateCellGroupPosition( - cellGroup, - (colGroup.lastChild as Group).row + 1, - (colGroup.lastChild as Group).attribute.y + (colGroup.lastChild as Group).attribute.height - ); - colGroup.appendChild(cellGroup); - } else { - const cellGroup = colGroup.lastChild as Group; - this.updateCellGroupPosition( - cellGroup, - (colGroup.firstChild as Group).row - 1, - (colGroup.firstChild as Group).attribute.y - cellGroup.attribute.height - ); - colGroup.insertBefore(cellGroup, colGroup.firstChild); - } - } - } - const distStartRow = direction === 'up' ? this.rowEnd + 1 : this.rowStart - count; - const distEndRow = direction === 'up' ? this.rowEnd + count : this.rowStart - 1; - - // 更新同步范围 - const syncTopRow = Math.max(this.bodyTopRow, screenTopRow - this.screenRowCount * 2); - const syncBottomRow = Math.min(this.bodyBottomRow, screenTopRow + this.screenRowCount * 3); - if (this.table.internalProps.autoRowHeight) { - computeRowsHeight(this.table, syncTopRow, syncBottomRow); - } - for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { - for (let row = syncTopRow; row <= syncBottomRow; row++) { - // const cellGroup = this.table.scenegraph.getCell(col, row); - const cellGroup = this.highPerformanceGetCell(col, row, distStartRow, distEndRow); - this.updateCellGroupContent(cellGroup); - } - } - if (this.table.internalProps.autoRowHeight) { - updateAutoRow( - this.bodyLeftCol, // colStart - this.bodyRightCol, // colEnd - syncTopRow, // rowStart - syncBottomRow, // rowEnd - this.table, - direction - ); - } - - this.rowStart = direction === 'up' ? this.rowStart + count : this.rowStart - count; - this.rowEnd = direction === 'up' ? this.rowEnd + count : this.rowEnd - count; - this.currentRow = direction === 'up' ? this.currentRow + count : this.currentRow - count; - this.totalRow = direction === 'up' ? this.totalRow + count : this.totalRow - count; - this.referenceRow = this.rowStart + Math.floor((this.rowEnd - this.rowStart) / 2); - this.rowUpdatePos = distStartRow; - this.rowUpdateDirection = direction; - console.log('move end proxy', this.rowStart, this.rowEnd); - console.log( - 'move end cell', - (this.table as any).scenegraph.bodyGroup.firstChild.firstChild.row, - (this.table as any).scenegraph.bodyGroup.firstChild.lastChild.row - ); - - this.table.scenegraph.stage.render(); - await this.progress(); - } else { - const distStartRow = direction === 'up' ? this.rowStart + count : this.rowStart - count; - const distEndRow = direction === 'up' ? this.rowEnd + count : this.rowEnd - count; - const distStartRowY = this.table.getRowsHeight(this.bodyTopRow, distStartRow - 1); - for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { - const colGroup = this.table.scenegraph.getColGroup(col); - colGroup?.forEachChildren((cellGroup: Group, index) => { - // 这里使用colGroup变量而不是for this.rowStart to this.rowEndthis.rowEnd是因为在更新内可能出现row号码重复的情况 - this.updateCellGroupPosition( - cellGroup, - direction === 'up' ? cellGroup.row + count : cellGroup.row - count, - index === 0 // row === this.rowStart - ? distStartRowY - : (cellGroup._prev as Group).attribute.y + (cellGroup._prev as Group).attribute.height - ); - }); - } - - // 更新同步范围 - let syncTopRow; - let syncBottomRow; - if (this.table.internalProps.autoRowHeight) { - syncTopRow = distStartRow; - syncBottomRow = distEndRow; - } else { - syncTopRow = Math.max(this.bodyTopRow, screenTopRow - this.screenRowCount * 2); - syncBottomRow = Math.min(this.bodyBottomRow, screenTopRow + this.screenRowCount * 3); - } - console.log('更新同步范围', syncTopRow, syncBottomRow); - if (this.table.internalProps.autoRowHeight) { - computeRowsHeight(this.table, syncTopRow, syncBottomRow); - } - for (let col = this.bodyLeftCol; col <= this.bodyRightCol; col++) { - for (let row = syncTopRow; row <= syncBottomRow; row++) { - // const cellGroup = this.table.scenegraph.getCell(col, row); - const cellGroup = this.highPerformanceGetCell(col, row, distStartRow, distEndRow); - this.updateCellGroupContent(cellGroup); - } - } - console.log('updateAutoRow', distEndRow > this.bodyBottomRow - (this.rowEnd - this.rowStart + 1) ? 'down' : 'up'); - - if (this.table.internalProps.autoRowHeight) { - updateAutoRow( - this.bodyLeftCol, // colStart - this.bodyRightCol, // colEnd - syncTopRow, // rowStart - syncBottomRow, // rowEnd - this.table, - distEndRow > this.bodyBottomRow - (this.rowEnd - this.rowStart + 1) ? 'down' : 'up' // 跳转到底部时,从下向上对齐 - ); - } - - this.rowStart = distStartRow; - this.rowEnd = distEndRow; - this.currentRow = direction === 'up' ? this.currentRow + count : this.currentRow - count; - this.totalRow = direction === 'up' ? this.totalRow + count : this.totalRow - count; - this.referenceRow = this.rowStart + Math.floor((this.rowEnd - this.rowStart) / 2); - this.rowUpdatePos = this.rowStart; - this.rowUpdateDirection = distEndRow > this.bodyBottomRow - (this.rowEnd - this.rowStart + 1) ? 'down' : 'up'; - console.log('move total end proxy', this.rowStart, this.rowEnd); - console.log( - 'move total end cell', - (this.table as any).scenegraph.bodyGroup.firstChild.firstChild.row, - (this.table as any).scenegraph.bodyGroup.firstChild.lastChild.row - ); - - if (!this.table.internalProps.autoRowHeight) { - await this.progress(); - } - } - } - - async updateCellGroupsAsync() { + async updateRowCellGroupsAsync() { this.updateCellGroups(this.taskRowCount); } updateCellGroups(count: number) { const distRow = Math.min(this.bodyBottomRow, this.rowUpdatePos + count); - console.log('updateCellGroups', this.rowUpdatePos, distRow); + // console.log('updateCellGroups', this.rowUpdatePos, distRow); if (this.table.internalProps.autoRowHeight) { computeRowsHeight(this.table, this.rowUpdatePos, distRow); } @@ -463,6 +472,36 @@ export class SceneProxy { this.rowUpdatePos = distRow + 1; } + async updateColCellGroupsAsync() { + this.updateColGroups(this.taskRowCount); + } + + updateColGroups(count: number) { + const distCol = Math.min(this.bodyRightCol, this.colUpdatePos + count); + // console.log('updateCellGroups', this.colUpdatePos, distCol); + for (let col = this.colUpdatePos; col <= distCol; col++) { + const colGroup = this.table.scenegraph.getColGroup(col); + if (colGroup) { + // colGroup.forEachChildren((cellGroup: Group) => { + // this.updateCellGroupContent(cellGroup); + // }); + // for (let row = (colGroup.firstChild as Group).row; row <= (colGroup.lastChild as Group).row; row++) { + // const cellGroup = this.highPerformanceGetCell(colGroup.col, row); + // this.updateCellGroupContent(cellGroup); + // } + let cellGroup = colGroup.firstChild; + while (cellGroup) { + // this.updateCellGroupContent(cellGroup as Group); + // cellGroup = cellGroup._next; + const newCellGroup = this.updateCellGroupContent(cellGroup as Group); + cellGroup = newCellGroup._next; + } + colGroup.needUpdate = false; + } + } + this.colUpdatePos = distCol + 1; + } + updateCellGroupPosition(cellGroup: Group, newRow: number, y: number) { // 更新位置&row cellGroup.row = newRow; @@ -473,10 +512,10 @@ export class SceneProxy { updateCellGroupContent(cellGroup: Group) { if (!cellGroup.needUpdate) { - return; + return cellGroup; } - this.table.scenegraph.updateCellContent(cellGroup.col, cellGroup.row); + const newCellGroup = this.table.scenegraph.updateCellContent(cellGroup.col, cellGroup.row); // 更新内容 // const textMark = cellGroup.firstChild as WrapText; // const autoWrapText = Array.isArray(textMark.attribute.text); @@ -491,6 +530,7 @@ export class SceneProxy { // textMark.setAttribute('text', text); cellGroup.needUpdate = false; + return newCellGroup || cellGroup; } async sortCell() { @@ -555,7 +595,13 @@ export class SceneProxy { } } - highPerformanceGetCell(col: number, row: number, rowStart: number = this.rowStart, rowEnd: number = this.rowEnd) { + highPerformanceGetCell( + col: number, + row: number, + rowStart: number = this.rowStart, + rowEnd: number = this.rowEnd, + getShadow?: boolean + ) { if (row < rowStart || row > rowEnd) { return emptyGroup; } @@ -565,93 +611,21 @@ export class SceneProxy { // 由缓存单元格向前后查找要快于从头查找 let cellGroup = getCellByCache(cacheCellGoup, row); if (!cellGroup) { - cellGroup = this.table.scenegraph.getCell(col, row); + cellGroup = this.table.scenegraph.getCell(col, row, getShadow); } cellGroup.row && this.cellCache.set(col, cellGroup); return cellGroup; } - const cellGroup = this.table.scenegraph.getCell(col, row); + const cellGroup = this.table.scenegraph.getCell(col, row, getShadow); cellGroup.row && this.cellCache.set(col, cellGroup); return cellGroup; } - const cellGroup = this.table.scenegraph.getCell(col, row); + const cellGroup = this.table.scenegraph.getCell(col, row, getShadow); cellGroup.row && this.cellCache.set(col, cellGroup); return cellGroup; } } -function updateAutoRow( - colStart: number, - colEnd: number, - rowStart: number, - rowEnd: number, - table: BaseTableAPI, - direction: 'up' | 'down' = 'up' -) { - // // 获取行高 - // for (let row = rowStart; row <= rowEnd; row++) { - // let maxRowHeight = 0; - // for (let col = colStart; col <= colEnd; col++) { - // const cellGroup = table.scenegraph.highPerformanceGetCell(col, row); - // if (!cellGroup.row) { - // continue; - // } - // // const contentHeight = cellGroup.getContentHeight(); - // const text = (cellGroup.getChildByName('text') as WrapText) || cellGroup.getChildByName('content'); - // const headerStyle = table._getCellStyle(col, row); - // const padding = getQuadProps(getProp('padding', headerStyle, col, row, table)); - // const height = text.AABBBounds.height() + (padding[0] + padding[2]); - // maxRowHeight = Math.max(maxRowHeight, height); - // (cellGroup as any).needUpdateForAutoRowHeight = false; - // } - // // updateRowHeight(table.scenegraph, row, table.getRowHeight(row) - maxRowHeight); - // for (let col = colStart; col <= colEnd; col++) { - // const cellGroup = table.scenegraph.highPerformanceGetCell(col, row); - // updateCellHeightForColumn(table.scenegraph, cellGroup, col, row, maxRowHeight, 0, false); - // } - - // table.setRowHeight(row, maxRowHeight, true); - // } - - // 更新y位置 - if (direction === 'up') { - for (let col = colStart; col <= colEnd; col++) { - for (let row = rowStart; row <= rowEnd; row++) { - const cellGroup = table.scenegraph.getCell(col, row); - if (!cellGroup.row) { - continue; - } - let y; - if (cellGroup._prev) { - y = ((cellGroup._prev as Group)?.attribute.y ?? 0) + ((cellGroup._prev as Group)?.attribute.height ?? 0); - } else { - // 估计位置 - y = table.getRowsHeight(table.columnHeaderLevelCount, cellGroup.row - 1); - } - cellGroup.setAttribute('y', y); - } - } - } else { - for (let col = colStart; col <= colEnd; col++) { - for (let row = rowEnd; row >= rowStart; row--) { - const cellGroup = table.scenegraph.getCell(col, row); - if (!cellGroup.row) { - continue; - } - let y; - if (cellGroup._next) { - y = ((cellGroup._next as Group)?.attribute.y ?? 0) - (cellGroup.attribute.height ?? 0); - } else { - // 估计位置 - y = table.getRowsHeight(table.columnHeaderLevelCount, cellGroup.row) - (cellGroup.attribute.height ?? 0); - console.log('估计位置', table.getRowsHeight(table.columnHeaderLevelCount, cellGroup.row)); - } - cellGroup.setAttribute('y', y); - } - } - } -} - function getCellByCache(cacheCellGroup: Group, row: number): Group | null { if (!cacheCellGroup) { return null; diff --git a/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-x.ts b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-x.ts new file mode 100644 index 000000000..caf402584 --- /dev/null +++ b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-x.ts @@ -0,0 +1,184 @@ +import type { BaseTableAPI } from '../../../../ts-types/base-table'; +import type { Group } from '../../../graphic/group'; +import { computeColsWidth } from '../../../layout/compute-col-width'; +import type { SceneProxy } from '../proxy'; + +export async function dynamicSetX(x: number, proxy: SceneProxy) { + const screenLeft = (proxy.table as BaseTableAPI).getTargetColAt( + x + proxy.table.scenegraph.rowHeaderGroup.attribute.width + ); + if (!screenLeft) { + return; + } + + proxy.screenLeftCol = screenLeft.col; + const deltaCol = proxy.screenLeftCol - proxy.referenceCol; + + if (deltaCol > 0) { + // 向右滚动,左部column group移到右部 + proxy.table.scenegraph.setBodyAndColHeaderX(-x); + await moveColumn(deltaCol, 'left', proxy.screenLeftCol, proxy); + } else if (deltaCol < 0) { + // 向左滚动,右部cell group移到左部 + proxy.table.scenegraph.setBodyAndColHeaderX(-x); + await moveColumn(-deltaCol, 'right', proxy.screenLeftCol, proxy); + } else { + // 不改变row,更新body group范围 + proxy.table.scenegraph.setBodyAndColHeaderX(-x); + } + + proxy.table.scenegraph.updateNextFrame(); +} + +async function moveColumn(count: number, direction: 'left' | 'right', screenLeftCol: number, proxy: SceneProxy) { + // 限制count范围 + if (direction === 'left' && proxy.colEnd + count > proxy.bodyRightCol) { + count = proxy.bodyRightCol - proxy.colEnd; + } else if (direction === 'right' && proxy.colStart - count < proxy.bodyLeftCol) { + count = proxy.colStart - proxy.bodyLeftCol; + } + + const bodyGroup = proxy.table.scenegraph.bodyGroup; + + // 两种更新模式 + // 1. count < colEnd - colStart:从顶/底部移动count数量的单元格到底/顶部 + // 2. count >= colEnd - colStart:整体移动到目标位置 + if (count < proxy.colEnd - proxy.colStart) { + // 计算更新区域 + const startCol = direction === 'left' ? proxy.colStart : proxy.colEnd - count + 1; + const endCol = direction === 'left' ? proxy.colStart + count - 1 : proxy.colEnd; + const distStartCol = direction === 'left' ? proxy.colEnd + 1 : proxy.colStart - count; + const distEndCol = direction === 'left' ? proxy.colEnd + count : proxy.colStart - 1; + // update column width + computeColsWidth(proxy.table, distStartCol, distEndCol); + + // console.log('move', startCol, endCol, direction); + for (let col = startCol; col <= endCol; col++) { + if (direction === 'left') { + const colGroup = bodyGroup.firstChild as Group; + updateColGroupPosition( + colGroup, + (bodyGroup.lastChild as Group).col + 1, + (bodyGroup.lastChild as Group).attribute.x + (bodyGroup.lastChild as Group).attribute.width + ); + bodyGroup.appendChild(colGroup); + } else { + const colGroup = bodyGroup.lastChild as Group; + updateColGroupPosition( + colGroup, + (bodyGroup.firstChild as Group).col - 1, + (bodyGroup.firstChild as Group).attribute.x - proxy.table.getColWidth((bodyGroup.firstChild as Group).col - 1) + ); + bodyGroup.insertBefore(colGroup, bodyGroup.firstChild); + } + } + + // 更新同步范围 + const syncLeftCol = Math.max(proxy.bodyLeftCol, screenLeftCol - proxy.screenColCount * 1); + const syncRightCol = Math.min(proxy.bodyRightCol, screenLeftCol + proxy.screenColCount * 2); + for (let col = syncLeftCol; col <= syncRightCol; col++) { + const colGroup = proxy.table.scenegraph.getColGroup(col); + updateColGroupContent(colGroup, proxy); + } + + proxy.colStart = direction === 'left' ? proxy.colStart + count : proxy.colStart - count; + proxy.colEnd = direction === 'left' ? proxy.colEnd + count : proxy.colEnd - count; + proxy.currentCol = direction === 'left' ? proxy.currentCol + count : proxy.currentCol - count; + proxy.totalCol = direction === 'left' ? proxy.totalCol + count : proxy.totalCol - count; + proxy.referenceCol = proxy.colStart + Math.floor((proxy.colEnd - proxy.colStart) / 2); + proxy.colUpdatePos = distStartCol; + proxy.colUpdateDirection = direction; + // console.log('col move end proxy', proxy.colStart, proxy.colEnd); + // console.log( + // 'col move end cell col', + // (proxy.table as any).scenegraph.bodyGroup.firstChild.col, + // (proxy.table as any).scenegraph.bodyGroup.lastChild.col + // ); + // console.log('sync', proxy.referenceCol, proxy.colStart, proxy.colEnd); + + proxy.table.scenegraph.stage.render(); + + // 开始异步任务 + await proxy.progress(); + } else { + const distStartCol = direction === 'left' ? proxy.colStart + count : proxy.colStart - count; + const distEndCol = direction === 'left' ? proxy.colEnd + count : proxy.colEnd - count; + + // update column width + computeColsWidth(proxy.table, distStartCol, distEndCol); + const distStartColY = proxy.table.getColsWidth(proxy.bodyLeftCol, distStartCol - 1); + console.log('distStartColY', proxy.bodyLeftCol, distStartCol - 1, distStartColY); + + bodyGroup.forEachChildren((colGroup: Group, index) => { + if (colGroup.type === 'group') { + updateColGroupPosition( + colGroup, + direction === 'left' ? colGroup.col + count : colGroup.col - count, + // (bodyGroup.lastChild as Group).attribute.x + (bodyGroup.lastChild as Group).attribute.width + index === 0 // row === proxy.rowStart + ? distStartColY + : (colGroup._prev as Group).attribute.x + proxy.table.getColWidth((colGroup._prev as Group).col) + ); + } + }); + + // 更新同步范围 + const syncLeftCol = Math.max(proxy.bodyLeftCol, screenLeftCol - proxy.screenRowCount * 1); + const syncRightCol = Math.min(proxy.bodyRightCol, screenLeftCol + proxy.screenRowCount * 2); + // console.log('更新同步范围col', syncLeftCol, syncRightCol); + for (let col = syncLeftCol; col <= syncRightCol; col++) { + const colGroup = proxy.table.scenegraph.getColGroup(col); + updateColGroupContent(colGroup, proxy); + } + + // for test + const cellGroup = proxy.table.scenegraph.getCell(screenLeftCol, 0); + cellGroup.AABBBounds.width(); + console.log('leftCell', cellGroup.col, cellGroup.globalAABBBounds, cellGroup); + + proxy.colStart = distStartCol; + proxy.colEnd = distEndCol; + proxy.currentCol = direction === 'left' ? proxy.currentCol + count : proxy.currentCol - count; + proxy.totalCol = direction === 'left' ? proxy.totalCol + count : proxy.totalCol - count; + proxy.referenceCol = proxy.colStart + Math.floor((proxy.colEnd - proxy.colStart) / 2); + proxy.colUpdatePos = proxy.colStart; + proxy.colUpdateDirection = distEndCol > proxy.bodyRightCol - (proxy.colEnd - proxy.colStart + 1) ? 'right' : 'left'; + // console.log('sync', proxy.referenceCol, proxy.colStart, proxy.colEnd); + // console.log('move total end proxy col', proxy.colStart, proxy.colEnd); + // console.log( + // 'move total end cell col', + // (proxy.table as any).scenegraph.bodyGroup.firstChild.row, + // (proxy.table as any).scenegraph.bodyGroup.lastChild.row + // ); + // proxy.table.scenegraph.stage.render(); + + await proxy.progress(); + } +} + +function updateColGroupPosition(colGroup: Group, newCol: number, x: number) { + // 更新位置&col + colGroup.col = newCol; + colGroup.forEachChildren((cellGroup: Group) => { + cellGroup.col = newCol; + cellGroup.needUpdate = true; + }); + colGroup.setAttribute('x', x); + colGroup.needUpdate = true; +} + +function updateColGroupContent(colGroup: Group, proxy: SceneProxy) { + // colGroup.forEachChildren((cellGroup: Group) => { + // proxy.updateCellGroupContent(cellGroup); + // }); + // for (let row = (colGroup.firstChild as Group).row; row <= (colGroup.lastChild as Group).row; row++) { + // const cellGroup = proxy.highPerformanceGetCell(colGroup.col, row); + // proxy.updateCellGroupContent(cellGroup); + // } + let cellGroup = colGroup.firstChild; + while (cellGroup) { + const newCellGroup = proxy.updateCellGroupContent(cellGroup as Group); + cellGroup = newCellGroup._next; + } + colGroup.needUpdate = false; +} diff --git a/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts new file mode 100644 index 000000000..f318f234c --- /dev/null +++ b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts @@ -0,0 +1,198 @@ +import type { Group } from '../../../graphic/group'; +import { computeRowsHeight } from '../../../layout/compute-row-height'; +import type { SceneProxy } from '../proxy'; +import { updateAutoRow } from './update-auto-row'; + +export async function dynamicSetY(y: number, proxy: SceneProxy) { + // 计算变动row range + // const screenTopRow = proxy.table.getRowAt(y).row; + const screenTop = (proxy.table as any).getTargetRowAt(y + proxy.table.scenegraph.colHeaderGroup.attribute.height); + if (!screenTop) { + return; + } + const screenTopRow = screenTop.row; + proxy.screenTopRow = screenTopRow; + const deltaRow = screenTopRow - proxy.referenceRow; + if (deltaRow > 0) { + // 向下滚动,顶部cell group移到底部 + moveCell(deltaRow, 'up', screenTopRow, proxy); + proxy.updateBody(y); + // if (proxy.rowEnd === proxy.table.scenegraph.proxy.bodyBottomRow) { + // const totalHeight = proxy.table.getAllRowsHeight(); + // const top = totalHeight - proxy.table.scenegraph.height; + // proxy.updateBody(top); + // } else { + // proxy.updateBody(y); + // } + } else if (deltaRow < 0) { + // 向上滚动,底部cell group移到顶部 + moveCell(-deltaRow, 'down', screenTopRow, proxy); + proxy.updateBody(y); + // if (proxy.rowStart === proxy.bodyTopRow) { + // proxy.updateBody(0); + // } else { + // proxy.updateBody(y); + // } + } else { + // 不改变row,更新body group范围 + proxy.updateBody(y); + } + + proxy.table.scenegraph.updateNextFrame(); +} + +async function moveCell(count: number, direction: 'up' | 'down', screenTopRow: number, proxy: SceneProxy) { + // 限制count范围 + if (direction === 'up' && proxy.rowEnd + count > proxy.bodyBottomRow) { + count = proxy.bodyBottomRow - proxy.rowEnd; + } else if (direction === 'down' && proxy.rowStart - count < proxy.bodyTopRow) { + count = proxy.rowStart - proxy.bodyTopRow; + } + + // 两种更新模式 + // 1. count < rowEnd - rowStart:从顶/底部移动count数量的单元格到底/顶部 + // 2. count >= rowEnd - rowStart:整体移动到目标位置 + if (count < proxy.rowEnd - proxy.rowStart) { + // 计算更新区域 + const startRow = direction === 'up' ? proxy.rowStart : proxy.rowEnd - count + 1; + const endRow = direction === 'up' ? proxy.rowStart + count - 1 : proxy.rowEnd; + // console.log('move', startRow, endRow, direction); + for (let col = proxy.bodyLeftCol; col <= proxy.bodyRightCol; col++) { + const colGroup = proxy.table.scenegraph.getColGroup(col); + for (let row = startRow; row <= endRow; row++) { + if (direction === 'up') { + const cellGroup = colGroup.firstChild as Group; + proxy.updateCellGroupPosition( + cellGroup, + (colGroup.lastChild as Group).row + 1, + (colGroup.lastChild as Group).attribute.y + (colGroup.lastChild as Group).attribute.height + ); + colGroup.appendChild(cellGroup); + } else { + const cellGroup = colGroup.lastChild as Group; + proxy.updateCellGroupPosition( + cellGroup, + (colGroup.firstChild as Group).row - 1, + (colGroup.firstChild as Group).attribute.y - cellGroup.attribute.height + ); + colGroup.insertBefore(cellGroup, colGroup.firstChild); + } + } + } + const distStartRow = direction === 'up' ? proxy.rowEnd + 1 : proxy.rowStart - count; + const distEndRow = direction === 'up' ? proxy.rowEnd + count : proxy.rowStart - 1; + + // 更新同步范围 + const syncTopRow = Math.max(proxy.bodyTopRow, screenTopRow - proxy.screenRowCount * 1); + const syncBottomRow = Math.min(proxy.bodyBottomRow, screenTopRow + proxy.screenRowCount * 2); + if (proxy.table.internalProps.autoRowHeight) { + computeRowsHeight(proxy.table, syncTopRow, syncBottomRow); + } + for (let col = proxy.bodyLeftCol; col <= proxy.bodyRightCol; col++) { + for (let row = syncTopRow; row <= syncBottomRow; row++) { + // const cellGroup = proxy.table.scenegraph.getCell(col, row); + const cellGroup = proxy.highPerformanceGetCell(col, row, distStartRow, distEndRow); + proxy.updateCellGroupContent(cellGroup); + } + } + if (proxy.table.internalProps.autoRowHeight) { + updateAutoRow( + proxy.bodyLeftCol, // colStart + proxy.bodyRightCol, // colEnd + syncTopRow, // rowStart + syncBottomRow, // rowEnd + proxy.table, + direction + ); + } + + proxy.rowStart = direction === 'up' ? proxy.rowStart + count : proxy.rowStart - count; + proxy.rowEnd = direction === 'up' ? proxy.rowEnd + count : proxy.rowEnd - count; + proxy.currentRow = direction === 'up' ? proxy.currentRow + count : proxy.currentRow - count; + proxy.totalRow = direction === 'up' ? proxy.totalRow + count : proxy.totalRow - count; + proxy.referenceRow = proxy.rowStart + Math.floor((proxy.rowEnd - proxy.rowStart) / 2); + proxy.rowUpdatePos = distStartRow; + proxy.rowUpdateDirection = direction; + console.log('move end proxy', proxy.rowStart, proxy.rowEnd); + console.log( + 'move end cell', + (proxy.table as any).scenegraph.bodyGroup.firstChild.firstChild.row, + (proxy.table as any).scenegraph.bodyGroup.firstChild.lastChild.row + ); + + proxy.table.scenegraph.stage.render(); + await proxy.progress(); + } else { + const distStartRow = direction === 'up' ? proxy.rowStart + count : proxy.rowStart - count; + const distEndRow = direction === 'up' ? proxy.rowEnd + count : proxy.rowEnd - count; + const distStartRowY = proxy.table.getRowsHeight(proxy.bodyTopRow, distStartRow - 1); + for (let col = proxy.bodyLeftCol; col <= proxy.bodyRightCol; col++) { + const colGroup = proxy.table.scenegraph.getColGroup(col); + colGroup?.forEachChildren((cellGroup: Group, index) => { + // 这里使用colGroup变量而不是for proxy.rowStart to proxy.rowEndproxy.rowEnd是因为在更新内可能出现row号码重复的情况 + proxy.updateCellGroupPosition( + cellGroup, + direction === 'up' ? cellGroup.row + count : cellGroup.row - count, + index === 0 // row === proxy.rowStart + ? distStartRowY + : (cellGroup._prev as Group).attribute.y + (cellGroup._prev as Group).attribute.height + ); + }); + } + + // 更新同步范围 + let syncTopRow; + let syncBottomRow; + if (proxy.table.internalProps.autoRowHeight) { + syncTopRow = distStartRow; + syncBottomRow = distEndRow; + } else { + syncTopRow = Math.max(proxy.bodyTopRow, screenTopRow - proxy.screenRowCount * 1); + syncBottomRow = Math.min(proxy.bodyBottomRow, screenTopRow + proxy.screenRowCount * 2); + } + console.log('更新同步范围', syncTopRow, syncBottomRow); + if (proxy.table.internalProps.autoRowHeight) { + computeRowsHeight(proxy.table, syncTopRow, syncBottomRow); + } + for (let col = proxy.bodyLeftCol; col <= proxy.bodyRightCol; col++) { + for (let row = syncTopRow; row <= syncBottomRow; row++) { + // const cellGroup = proxy.table.scenegraph.getCell(col, row); + const cellGroup = proxy.highPerformanceGetCell(col, row, distStartRow, distEndRow); + proxy.updateCellGroupContent(cellGroup); + } + } + console.log( + 'updateAutoRow', + distEndRow > proxy.bodyBottomRow - (proxy.rowEnd - proxy.rowStart + 1) ? 'down' : 'up' + ); + + if (proxy.table.internalProps.autoRowHeight) { + updateAutoRow( + proxy.bodyLeftCol, // colStart + proxy.bodyRightCol, // colEnd + syncTopRow, // rowStart + syncBottomRow, // rowEnd + proxy.table, + distEndRow > proxy.bodyBottomRow - (proxy.rowEnd - proxy.rowStart + 1) ? 'down' : 'up' // 跳转到底部时,从下向上对齐 + ); + } + + proxy.rowStart = distStartRow; + proxy.rowEnd = distEndRow; + proxy.currentRow = direction === 'up' ? proxy.currentRow + count : proxy.currentRow - count; + proxy.totalRow = direction === 'up' ? proxy.totalRow + count : proxy.totalRow - count; + proxy.referenceRow = proxy.rowStart + Math.floor((proxy.rowEnd - proxy.rowStart) / 2); + proxy.rowUpdatePos = proxy.rowStart; + proxy.rowUpdateDirection = distEndRow > proxy.bodyBottomRow - (proxy.rowEnd - proxy.rowStart + 1) ? 'down' : 'up'; + console.log('move total end proxy', proxy.rowStart, proxy.rowEnd); + console.log( + 'move total end cell', + (proxy.table as any).scenegraph.bodyGroup.firstChild.firstChild.row, + (proxy.table as any).scenegraph.bodyGroup.firstChild.lastChild.row + ); + + if (!proxy.table.internalProps.autoRowHeight) { + await proxy.progress(); + } + } +} diff --git a/packages/vtable/src/scenegraph/group-creater/progress/update-position/update-auto-row.ts b/packages/vtable/src/scenegraph/group-creater/progress/update-position/update-auto-row.ts new file mode 100644 index 000000000..2b8e9f6c1 --- /dev/null +++ b/packages/vtable/src/scenegraph/group-creater/progress/update-position/update-auto-row.ts @@ -0,0 +1,74 @@ +import type { BaseTableAPI } from '../../../../ts-types/base-table'; +import type { Group } from '../../../graphic/group'; + +export function updateAutoRow( + colStart: number, + colEnd: number, + rowStart: number, + rowEnd: number, + table: BaseTableAPI, + direction: 'up' | 'down' = 'up' +) { + // // 获取行高 + // for (let row = rowStart; row <= rowEnd; row++) { + // let maxRowHeight = 0; + // for (let col = colStart; col <= colEnd; col++) { + // const cellGroup = table.scenegraph.highPerformanceGetCell(col, row); + // if (!cellGroup.row) { + // continue; + // } + // // const contentHeight = cellGroup.getContentHeight(); + // const text = (cellGroup.getChildByName('text') as WrapText) || cellGroup.getChildByName('content'); + // const headerStyle = table._getCellStyle(col, row); + // const padding = getQuadProps(getProp('padding', headerStyle, col, row, table)); + // const height = text.AABBBounds.height() + (padding[0] + padding[2]); + // maxRowHeight = Math.max(maxRowHeight, height); + // (cellGroup as any).needUpdateForAutoRowHeight = false; + // } + // // updateRowHeight(table.scenegraph, row, table.getRowHeight(row) - maxRowHeight); + // for (let col = colStart; col <= colEnd; col++) { + // const cellGroup = table.scenegraph.highPerformanceGetCell(col, row); + // updateCellHeightForColumn(table.scenegraph, cellGroup, col, row, maxRowHeight, 0, false); + // } + + // table.setRowHeight(row, maxRowHeight, true); + // } + + // 更新y位置 + if (direction === 'up') { + for (let col = colStart; col <= colEnd; col++) { + for (let row = rowStart; row <= rowEnd; row++) { + const cellGroup = table.scenegraph.getCell(col, row); + if (!cellGroup.row) { + continue; + } + let y; + if (cellGroup._prev) { + y = ((cellGroup._prev as Group)?.attribute.y ?? 0) + ((cellGroup._prev as Group)?.attribute.height ?? 0); + } else { + // 估计位置 + y = table.getRowsHeight(table.columnHeaderLevelCount, cellGroup.row - 1); + } + cellGroup.setAttribute('y', y); + } + } + } else { + for (let col = colStart; col <= colEnd; col++) { + for (let row = rowEnd; row >= rowStart; row--) { + const cellGroup = table.scenegraph.getCell(col, row); + if (!cellGroup.row) { + continue; + } + let y; + if (cellGroup._next) { + y = ((cellGroup._next as Group)?.attribute.y ?? 0) - (cellGroup.attribute.height ?? 0); + } else { + // 估计位置 + y = table.getRowsHeight(table.columnHeaderLevelCount, cellGroup.row) - (cellGroup.attribute.height ?? 0); + console.log('估计位置', table.getRowsHeight(table.columnHeaderLevelCount, cellGroup.row)); + } + cellGroup.setAttribute('y', y); + } + } + } +} diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts index 39a340ab7..7333ef218 100644 --- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts +++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts @@ -8,16 +8,27 @@ import { getQuadProps } from '../utils/padding'; import { getProp } from '../utils/get-prop'; import type { BaseTableAPI } from '../../ts-types/base-table'; -export function computeColsWidth(table: BaseTableAPI, update?: boolean): void { +export function computeColsWidth(table: BaseTableAPI, colStart?: number, colEnd?: number, update?: boolean): void { const time = typeof window !== 'undefined' ? window.performance.now() : 0; - table._clearColRangeWidthsMap(); + colStart = colStart ?? 0; + colEnd = colEnd ?? table.colCount - 1; + // table._clearColRangeWidthsMap(); + // clear colRangeWidthsMap + if (colStart === 0 && colEnd === table.colCount - 1) { + table._clearColRangeWidthsMap(); + // } else { + // for (let col = colStart; col <= colEnd; col++) { + // table._clearColRangeWidthsMap(col); + // } + } + const oldColWidths = []; if (update) { for (let col = 0; col < table.colCount; col++) { oldColWidths.push(table.getColWidth(col)); } } - for (let col = 0; col < table.colCount; col++) { + for (let col = colStart; col <= colEnd; col++) { let maxWidth; if ( !table.internalProps.transpose && @@ -44,7 +55,12 @@ export function computeColsWidth(table: BaseTableAPI, update?: boolean): void { } table._setColContentWidth(col, maxWidth); - table.setColWidth(col, maxWidth, false, true); + + const oldWidth = table.getColWidth(col); + if (oldWidth !== maxWidth) { + table._clearColRangeWidthsMap(col); + table.setColWidth(col, maxWidth, false, true); + } } // 处理adaptive宽度 if (table.widthMode === 'adaptive') { @@ -88,7 +104,7 @@ export function computeColsWidth(table: BaseTableAPI, update?: boolean): void { } } } - console.log('computeColsWidth time:', (typeof window !== 'undefined' ? window.performance.now() : 0) - time); + // console.log('computeColsWidth time:', (typeof window !== 'undefined' ? window.performance.now() : 0) - time); if (update) { for (let col = 0; col < table.colCount; col++) { diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts index 1801abe2a..eca220ad1 100644 --- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -28,6 +28,15 @@ export function computeRowsHeight(table: BaseTableAPI, rowStart?: number, rowEnd rowEnd = rowEnd ?? table.rowCount - 1; const time = typeof window !== 'undefined' ? window.performance.now() : 0; + // clear rowRangeHeightsMap + if (rowStart === 0 && rowEnd === table.rowCount - 1) { + table._clearRowRangeHeightsMap(); + } else { + for (let row = rowStart; row <= rowEnd; row++) { + table._clearRowRangeHeightsMap(row); + } + } + // compute header row in column header row for (let row = rowStart; row < table.columnHeaderLevelCount; row++) { const height = computeRowHeight(row, 0, table.colCount - 1, table); diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index ceebc45ae..cb064264a 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -286,13 +286,6 @@ export class Scenegraph { */ createSceneGraph() { this.clear = false; - computeColsWidth(this.table); - // compute the column header cell height - // table.rowHeightsMap.clear(); - this.table._clearRowRangeHeightsMap(); - this.table.internalProps._rowHeightsMap.clear(); - computeRowsHeight(this.table, 0, this.table.columnHeaderLevelCount - 1); - this.frozenColCount = this.table.rowHeaderLevelCount; this.frozenRowCount = this.table.columnHeaderLevelCount; @@ -301,50 +294,14 @@ export class Scenegraph { // update table group position for cell group global position, not create border yet. createFrameBorder(this.tableGroup, this.table.theme.frameStyle, this.tableGroup.role, undefined, true); - // 首屏表头全量 - this.createHeaderSceneGraph(); - // body生成首屏 - if (this.transpose || this.isPivot) { - this.createBodySceneGraph(); - } else { - this.createBodySceneGraphForFirstScreen(); - } - } - - createHeaderSceneGraph() { - createCornerHeaderColGroup(this.cornerHeaderGroup, 0, 0, this.table); - this.colHeaderGroup.setAttribute('x', this.cornerHeaderGroup.attribute.width); - createColHeaderColGroup(this.colHeaderGroup, 0, 0, this.table); - } - - createBodySceneGraph() { - // 依据列表头高度更新行表头Group y - this.rowHeaderGroup.setAttribute('y', this.colHeaderGroup.attribute.height); - createRowHeaderColGroup(this.rowHeaderGroup, 0, 0, this.table); - - // 依据列表头高度和行表头宽度,更新内容Group xy - this.bodyGroup.setAttributes({ - y: this.colHeaderGroup.attribute.height, - x: this.rowHeaderGroup.attribute.width - }); - createBodyColGroup(this.bodyGroup, 0, 0, this.table); - this.afterScenegraphCreated(); - } - - createBodySceneGraphForFirstScreen() { - // 依据列表头高度更新行表头Group y - this.rowHeaderGroup.setAttribute('y', this.colHeaderGroup.attribute.height); - // createRowHeaderColGroup(this.rowHeaderGroup, 0, 0, this.table); - // this.proxy.createBodyColGroupForFirstScreen(this.rowHeaderGroup, 0, 0, this.table, 'rowHeader'); - - // 依据列表头高度和行表头宽度,更新内容Group xy - this.bodyGroup.setAttributes({ - y: this.colHeaderGroup.attribute.height, - x: this.rowHeaderGroup.attribute.width - }); - console.log('before-createBodyColGroupForFirstScreen'); - this.proxy.createColGroupForFirstScreen(this.rowHeaderGroup, this.bodyGroup, 0, 0, this.table); - console.log('after-createBodyColGroupForFirstScreen'); + this.proxy.createGroupForFirstScreen( + this.cornerHeaderGroup, + this.colHeaderGroup, + this.rowHeaderGroup, + this.bodyGroup, + 0, + 0 + ); this.afterScenegraphCreated(); } @@ -430,11 +387,11 @@ export class Scenegraph { return cell || emptyGroup; } - highPerformanceGetCell(col: number, row: number): Group { - if (!this.isPivot && !this.transpose && !this.table.isHeader(col, row)) { - return this.proxy.highPerformanceGetCell(col, row, 0, this.table.rowCount - 1); + highPerformanceGetCell(col: number, row: number, getShadow?: boolean): Group { + if (!this.table.isHeader(col, row)) { + return this.proxy.highPerformanceGetCell(col, row, 0, this.table.rowCount - 1, getShadow); } - return this.getCell(col, row); + return this.getCell(col, row, getShadow); } getColGroup(col: number, isCornerOrColHeader = false): Group { @@ -710,7 +667,7 @@ export class Scenegraph { * recalculates column width in all autowidth columns */ recalculateColWidths() { - computeColsWidth(this.table, true); + computeColsWidth(this.table, 0, this.table.colCount - 1, true); } resize() { @@ -816,16 +773,17 @@ export class Scenegraph { * @return {*} */ setX(x: number) { - if (this.colHeaderGroup.attribute.width + x === this.bodyGroup.attribute.x) { - return; - } - // this.tableGroup.setAttribute('x', x); - // this.tableGroup.setAttribute('width', this.table.tableNoFrameWidth - x); - this.bodyGroup.setAttribute('x', this.rowHeaderGroup.attribute.width + x); - this.colHeaderGroup.setAttribute('x', this.rowHeaderGroup.attribute.width + x); + // if (this.colHeaderGroup.attribute.width + x === this.bodyGroup.attribute.x) { + // return; + // } + // // this.tableGroup.setAttribute('x', x); + // // this.tableGroup.setAttribute('width', this.table.tableNoFrameWidth - x); + // this.bodyGroup.setAttribute('x', this.rowHeaderGroup.attribute.width + x); + // this.colHeaderGroup.setAttribute('x', this.rowHeaderGroup.attribute.width + x); - // (this.tableGroup.lastChild as any).setAttribute('width', this.table.tableNoFrameWidth - x); - this.updateNextFrame(); + // // (this.tableGroup.lastChild as any).setAttribute('width', this.table.tableNoFrameWidth - x); + // this.updateNextFrame(); + this.table.scenegraph.proxy.setX(-x); } /** @@ -834,18 +792,18 @@ export class Scenegraph { * @return {*} */ setY(y: number) { - if (this.transpose || this.isPivot) { - if (this.colHeaderGroup.attribute.height + y === this.bodyGroup.attribute.y) { - return; - } - this.bodyGroup.setAttribute('y', this.colHeaderGroup.attribute.height + y); - this.rowHeaderGroup.setAttribute('y', this.colHeaderGroup.attribute.height + y); - // this.tableGroup.setAttribute('height', this.table.tableNoFrameHeight - y); - // (this.tableGroup.lastChild as any).setAttribute('width', this.table.tableNoFrameWidth - x); - this.updateNextFrame(); - } else if (this.table.scenegraph.proxy) { - this.table.scenegraph.proxy.setY(-y); - } + // if (this.transpose || this.isPivot) { + // if (this.colHeaderGroup.attribute.height + y === this.bodyGroup.attribute.y) { + // return; + // } + // this.bodyGroup.setAttribute('y', this.colHeaderGroup.attribute.height + y); + // this.rowHeaderGroup.setAttribute('y', this.colHeaderGroup.attribute.height + y); + // // this.tableGroup.setAttribute('height', this.table.tableNoFrameHeight - y); + // // (this.tableGroup.lastChild as any).setAttribute('width', this.table.tableNoFrameWidth - x); + // this.updateNextFrame(); + // } else if (this.table.scenegraph.proxy) { + this.table.scenegraph.proxy.setY(-y); + // } } /** @@ -864,6 +822,20 @@ export class Scenegraph { this.updateNextFrame(); } + /** + * @description: 更新表格的x位置,滚动中使用 + * @param {number} x + * @return {*} + */ + setBodyAndColHeaderX(x: number) { + if (this.rowHeaderGroup.attribute.width + x === this.bodyGroup.attribute.x) { + return; + } + this.bodyGroup.setAttribute('x', this.rowHeaderGroup.attribute.width + x); + this.colHeaderGroup.setAttribute('x', this.rowHeaderGroup.attribute.width + x); + this.updateNextFrame(); + } + /** * @description: 完成创建场景树节点后,处理自动行高列宽 * @return {*} @@ -1346,7 +1318,7 @@ export class Scenegraph { if (this.clear) { return; } - updateCell(col, row, this.table); + return updateCell(col, row, this.table); } setPixelRatio(pixelRatio: number) { diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 8337bea99..e1005232a 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -515,6 +515,9 @@ export interface BaseTableAPI { resize: () => void; getMergeCellRect: (col: number, row: number) => Rect; + + getTargetColAt: (absoluteX: number) => { col: number; left: number; right: number; width: number } | null; + getTargetRowAt: (absoluteY: number) => { row: number; top: number; bottom: number; height: number } | null; //#endregion tableAPI } export interface ListTableProtected extends IBaseTableProtected { From 4d864fe68c8831e04b4ef5f0eab9bba734b56fe3 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 6 Jul 2023 17:51:28 +0800 Subject: [PATCH 08/11] chore: update pnpm-lock.yaml --- common/config/rush/pnpm-lock.yaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index bb52645b8..306eb4ed3 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -62,7 +62,6 @@ importers: '@visactor/vrender-components': 0.11.0-alpha.3 '@visactor/vscale': 0.9.0-alpha.2 '@visactor/vutils': 0.9.0-alpha.2 - axios: 1.4.0 cssfontparser: 1.2.1 devDependencies: '@babel/core': 7.20.12 @@ -80,6 +79,7 @@ importers: '@types/react-dom': 16.9.8 '@visactor/vchart': 1.0.0 '@vitejs/plugin-react': 3.1.0_vite@3.2.6 + axios: 1.4.0 chai: 4.3.4 eslint: 8.18.0 form-data: 4.0.0 @@ -3291,6 +3291,7 @@ packages: /asynckit/0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true /atob/2.1.2: resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} @@ -3333,7 +3334,7 @@ packages: proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - dev: false + dev: true /babel-jest/24.9.0_@babel+core@7.20.12: resolution: {integrity: sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==} @@ -3982,6 +3983,7 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 + dev: true /commander/2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4463,6 +4465,7 @@ packages: /delayed-stream/1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dev: true /detect-file/1.0.0: resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} @@ -5551,7 +5554,7 @@ packages: peerDependenciesMeta: debug: optional: true - dev: false + dev: true /for-each/0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -5602,6 +5605,7 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 + dev: true /fraction.js/4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} @@ -8202,12 +8206,14 @@ packages: /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + dev: true /mime-types/2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 + dev: true /mime/1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} @@ -9491,7 +9497,7 @@ packages: /proxy-from-env/1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - dev: false + dev: true /prr/1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} From 1e1c88c01a1f06ca227b9082fa16e37ad913d82b Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 6 Jul 2023 17:53:08 +0800 Subject: [PATCH 09/11] fix: fix not all code paths return a value in updateCellContent() --- packages/vtable/src/scenegraph/scenegraph.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index cb064264a..cf62c7368 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -1316,7 +1316,7 @@ export class Scenegraph { updateCellContent(col: number, row: number) { if (this.clear) { - return; + return undefined; } return updateCell(col, row, this.table); } From f587d63683e110225c9b59c4f854249f8b9d08e0 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 7 Jul 2023 10:58:36 +0800 Subject: [PATCH 10/11] fix: fix reference computation in proxy module --- .../src/scenegraph/group-creater/progress/proxy.ts | 12 ++++++------ .../progress/update-position/dynamic-set-y.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts index c24f2bfb5..3394fd13d 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts @@ -197,22 +197,22 @@ export class SceneProxy { this.currentRow = this.totalRow; this.rowEnd = this.currentRow; this.rowUpdatePos = this.rowEnd; - this.referenceRow = Math.floor((this.rowEnd - this.rowStart) / 2); + this.referenceRow = this.rowStart + Math.floor((this.rowEnd - this.rowStart) / 2); this.currentCol = this.totalCol; this.colEnd = this.currentCol; this.colUpdatePos = this.colEnd; - this.referenceCol = Math.floor((this.colEnd - this.colStart) / 2); + this.referenceCol = this.colStart + Math.floor((this.colEnd - this.colStart) / 2); } else { this.currentRow = (bodyGroup.firstChild as Group)?.rowNumber ?? this.totalRow; this.rowEnd = this.currentRow; this.rowUpdatePos = this.rowEnd; - this.referenceRow = Math.floor((this.rowEnd - this.rowStart) / 2); + this.referenceRow = this.rowStart + Math.floor((this.rowEnd - this.rowStart) / 2); this.currentCol = (bodyGroup.lastChild as Group)?.col ?? this.totalCol; this.colEnd = this.currentCol; this.colUpdatePos = this.colEnd; - this.referenceCol = Math.floor((this.colEnd - this.colStart) / 2); + this.referenceCol = this.colStart + Math.floor((this.colEnd - this.colStart) / 2); // 开始异步任务 await this.progress(); @@ -359,7 +359,7 @@ export class SceneProxy { this.currentRow = endRow; this.rowEnd = endRow; this.rowUpdatePos = this.rowEnd; - this.referenceRow = Math.floor((endRow - this.rowStart) / 2); + this.referenceRow = this.rowStart + Math.floor((endRow - this.rowStart) / 2); // update container group size and border this.table.scenegraph.updateContainer(); @@ -395,7 +395,7 @@ export class SceneProxy { this.currentCol = endCol; this.colEnd = endCol; this.colUpdatePos = this.colEnd; - this.referenceCol = Math.floor((endCol - this.colStart) / 2); + this.referenceCol = this.colStart + Math.floor((endCol - this.colStart) / 2); console.log('async', this.referenceCol, this.colStart, this.colEnd); // update container group size and border diff --git a/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts index f318f234c..c6294971b 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts @@ -120,7 +120,7 @@ async function moveCell(count: number, direction: 'up' | 'down', screenTopRow: n (proxy.table as any).scenegraph.bodyGroup.firstChild.lastChild.row ); - proxy.table.scenegraph.stage.render(); + // proxy.table.scenegraph.stage.render(); await proxy.progress(); } else { const distStartRow = direction === 'up' ? proxy.rowStart + count : proxy.rowStart - count; From b8a970f32a7289767788081b18e40af7c4b78dc7 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 7 Jul 2023 10:59:49 +0800 Subject: [PATCH 11/11] feat: check textStick state before bind event --- packages/vtable/examples/menu.ts | 4 ++++ packages/vtable/src/event/event.ts | 10 ++++++---- .../vtable/src/scenegraph/stick-text/index.ts | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 81a5186e5..b8dd8803f 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -6,6 +6,10 @@ export const menus = [ path: 'list', name: 'list' }, + { + path: 'list', + name: 'list-transpose' + }, { path: 'list', name: 'list-tree' diff --git a/packages/vtable/src/event/event.ts b/packages/vtable/src/event/event.ts index 4d0131d55..4a0c53080 100644 --- a/packages/vtable/src/event/event.ts +++ b/packages/vtable/src/event/event.ts @@ -11,7 +11,7 @@ import { bindMediaClick } from './media-click'; import { bindDrillEvent, drillClick } from './drill'; import { bindSparklineHoverEvent } from './sparkline-event'; import type { BaseTableAPI } from '../ts-types/base-table'; -import { handleTextStick } from '../scenegraph/stick-text'; +import { checkHaveTextStick, handleTextStick } from '../scenegraph/stick-text'; import { bindTableGroupListener } from './listener/table-group'; import { bindScrollBarListener } from './listener/scroll-bar'; import { bindContainerDomListener } from './listener/container-dom'; @@ -74,9 +74,11 @@ export class EventManeger { }); // 处理textStick - this.table.listen(TABLE_EVENT_TYPE.SCROLL, e => { - handleTextStick(this.table); - }); + if (checkHaveTextStick(this.table)) { + this.table.listen(TABLE_EVENT_TYPE.SCROLL, e => { + handleTextStick(this.table); + }); + } // link/image/video点击 bindMediaClick(this.table); diff --git a/packages/vtable/src/scenegraph/stick-text/index.ts b/packages/vtable/src/scenegraph/stick-text/index.ts index a5a6f3fad..a0d74bb79 100644 --- a/packages/vtable/src/scenegraph/stick-text/index.ts +++ b/packages/vtable/src/scenegraph/stick-text/index.ts @@ -4,6 +4,7 @@ import { PIVOT_TABLE_EVENT_TYPE } from '../../ts-types/pivot-table/PIVOT_TABLE_E import type { Group } from '../graphic/group'; import type { WrapText } from '../graphic/text'; import type { PivotHeaderLayoutMap } from '../../layout/pivot-header-layout'; +import type { ITextStyleOption } from '../../ts-types'; const changedCells: { col: number; row: number }[] = []; export function handleTextStick(table: BaseTableAPI) { changedCells.forEach(cellPos => { @@ -133,3 +134,21 @@ function adjustCellContentHorizontalLayout(cellGroup: Group, minLeft: number, ma }); } } + +export function checkHaveTextStick(table: BaseTableAPI) { + const headerObjects = table.internalProps.layoutMap.headerObjects; + const columnObjects = table.internalProps.layoutMap.columnObjects; + for (let i = 0; i < headerObjects.length; i++) { + const header = headerObjects[i]; + if (header && (header.define.headerStyle as ITextStyleOption)?.textStick) { + return true; + } + } + for (let i = 0; i < columnObjects.length; i++) { + const column = columnObjects[i]; + if (column && (column.define.style as ITextStyleOption)?.textStick) { + return true; + } + } + return false; +}