From 6f7f411aed546ddb77436a548a1c3f41b14695ca Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 10 Jan 2024 17:28:44 +0800 Subject: [PATCH 01/63] refactor: vtable don't stop event bubble #892 --- ... cancel the bubbling of the table event.md | 44 +++++++++++++++++++ docs/assets/faq/menu.json | 7 +++ ... cancel the bubbling of the table event.md | 44 +++++++++++++++++++ packages/vtable/src/event/event.ts | 4 +- .../src/event/listener/container-dom.ts | 10 ++--- .../vtable/src/event/listener/table-group.ts | 15 ++++--- 6 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 docs/assets/faq/en/21-How to cancel the bubbling of the table event.md create mode 100644 docs/assets/faq/zh/21-How to cancel the bubbling of the table event.md diff --git a/docs/assets/faq/en/21-How to cancel the bubbling of the table event.md b/docs/assets/faq/en/21-How to cancel the bubbling of the table event.md new file mode 100644 index 000000000..f11da3e46 --- /dev/null +++ b/docs/assets/faq/en/21-How to cancel the bubbling of the table event.md @@ -0,0 +1,44 @@ +# How to cancel the bubbling of the table mousedown event + +## Problem Description + +In my business scenario, I need to drag the entire table to move the position. However, if the mouse point is dragged on the cell, it will trigger the box selection interaction of the table. In this way, I do not expect to drag the entire table. When the mouse point is clicked, Then respond to the entire table dragging behavior in the blank area of the table. + +Based on this demand background, how to determine whether the click is on a cell or a blank area of the table? + +## solution + +This problem can be handled in VTable by listening to the `mousedown_cell` event, but it should be noted that VTable internally listens to pointer events! + +Therefore, if you cancel bubbling directly, you can only cancel the pointerdown event. +``` + tableInstance.on('mousedown_cell', arg => { + arg.event.stopPropagation(); + }); +``` +Therefore, you need to listen to mousedown again to determine the organization event. For correct processing, you can see the following example: + +## Code Example + +```javascript + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + let isPointerDownOnTable = false; + tableInstance.on('mousedown_cell', arg => { + isPointerDownOnTable = true; + setTimeout(() => { + isPointerDownOnTable = false; + }, 0); + arg.event?.stopPropagation(); + }); + tableInstance.getElement().addEventListener('mousedown', e => { + if (isPointerDownOnTable) { + e.stopPropagation(); + } + }); +``` + +## Related documents + +- [Tutorial](https://visactor.io/vtable/guide/Event/event_list) +- [github](https://github.com/VisActor/VTable) \ No newline at end of file diff --git a/docs/assets/faq/menu.json b/docs/assets/faq/menu.json index 0bcc40396..540535d70 100644 --- a/docs/assets/faq/menu.json +++ b/docs/assets/faq/menu.json @@ -140,6 +140,13 @@ "zh": "20.如何在Vue中使用Vtable?", "en": "20.How to use VTable in Vue?" } + }, + { + "path": "21-How to cancel the bubbling of the table event", + "title": { + "zh": "21.怎么取消表格mousedown事件的冒泡?", + "en": "21.How to cancel the bubbling of the table event?" + } } ] } \ No newline at end of file diff --git a/docs/assets/faq/zh/21-How to cancel the bubbling of the table event.md b/docs/assets/faq/zh/21-How to cancel the bubbling of the table event.md new file mode 100644 index 000000000..6e51976e0 --- /dev/null +++ b/docs/assets/faq/zh/21-How to cancel the bubbling of the table event.md @@ -0,0 +1,44 @@ +# 怎么取消表格mousedown事件的冒泡 + +## 问题描述 + +在我的业务场景中需要对表格整体进行拖拽来移动位置,但是如果鼠标点在单元格上拖拽会触发表格的框选交互,这样我就预期不进行整表拖拽了,当鼠标点在表格空白区域再响应整表拖拽行为。 + +基于这个需求背景,怎么判断是点击在了单元格上还是表格空白区域呢? + +## 解决方案 + +在 VTable 中可以通过监听`mousedown_cell`事件来处理这个问题,不过需要注意的是VTable内部都是监听的pointer事件哦! + +所以如果直接取消冒泡,也仅能取消pointerdown事件。 +``` + tableInstance.on('mousedown_cell', arg => { + arg.event.stopPropagation(); + }); +``` +所以需要再监听mousedown来判断组织事件,正确处理可以看下面示例: + +## 代码示例 + +```javascript + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + let isPointerDownOnTable = false; + tableInstance.on('mousedown_cell', arg => { + isPointerDownOnTable = true; + setTimeout(() => { + isPointerDownOnTable = false; + }, 0); + arg.event?.stopPropagation(); + }); + tableInstance.getElement().addEventListener('mousedown', e => { + if (isPointerDownOnTable) { + e.stopPropagation(); + } + }); +``` + +## 相关文档 + +- [教程](https://visactor.io/vtable/guide/Event/event_list) +- [github](https://github.com/VisActor/VTable) diff --git a/packages/vtable/src/event/event.ts b/packages/vtable/src/event/event.ts index 31a068938..6bee297b8 100644 --- a/packages/vtable/src/event/event.ts +++ b/packages/vtable/src/event/event.ts @@ -28,8 +28,8 @@ export class EventManager { table: BaseTableAPI; // _col: number; // _resizing: boolean = false; - /** 为了能够判断canvas mousedown 事件 以阻止事件冒泡 */ - isPointerDownOnTable: boolean = false; + // /** 为了能够判断canvas mousedown 事件 以阻止事件冒泡 */ + // isPointerDownOnTable: boolean = false; isTouchdown: boolean; // touch scrolling mode on touchMovePoints: { x: number; diff --git a/packages/vtable/src/event/listener/container-dom.ts b/packages/vtable/src/event/listener/container-dom.ts index 01c3e6478..dfc398229 100644 --- a/packages/vtable/src/event/listener/container-dom.ts +++ b/packages/vtable/src/event/listener/container-dom.ts @@ -11,11 +11,11 @@ export function bindContainerDomListener(eventManager: EventManager) { const stateManager = table.stateManager; const handler: EventHandler = table.internalProps.handler; - handler.on(table.getElement(), 'mousedown', (e: MouseEvent) => { - if (table.eventManager.isPointerDownOnTable) { - e.stopPropagation(); - } - }); + // handler.on(table.getElement(), 'mousedown', (e: MouseEvent) => { + // if (table.eventManager.isPointerDownOnTable) { + // e.stopPropagation(); + // } + // }); handler.on(table.getElement(), 'blur', (e: MouseEvent) => { eventManager.dealTableHover(); diff --git a/packages/vtable/src/event/listener/table-group.ts b/packages/vtable/src/event/listener/table-group.ts index 685f307d4..a9d6e90e3 100644 --- a/packages/vtable/src/event/listener/table-group.ts +++ b/packages/vtable/src/event/listener/table-group.ts @@ -344,15 +344,16 @@ export function bindTableGroupListener(eventManager: EventManager) { table.scenegraph.tableGroup.addEventListener('pointerdown', (e: FederatedPointerEvent) => { console.log('tableGroup pointerdown'); - table.eventManager.isPointerDownOnTable = true; - setTimeout(() => { - table.eventManager.isPointerDownOnTable = false; - }, 0); + // table.eventManager.isPointerDownOnTable = true; + // setTimeout(() => { + // table.eventManager.isPointerDownOnTable = false; + // }, 0); table.eventManager.isDown = true; table.eventManager.LastBodyPointerXY = { x: e.x, y: e.y }; - // 避免在调整列宽等拖拽操作触发外层组件的拖拽逻辑 - // 如果鼠标位置在表格内(加调整列宽的热区),将mousedown事件阻止冒泡 - e.stopPropagation(); + // // 避免在调整列宽等拖拽操作触发外层组件的拖拽逻辑; + // // 如果鼠标位置在表格内(加调整列宽的热区),将pointerdown事件阻止冒泡(如果阻止mousedown需要结合isPointerDownOnTable来判断) + // e.stopPropagation(); + // e.preventDefault(); //为了阻止mousedown事件的触发,后续:不能这样写,会阻止table聚焦 table.eventManager.LastPointerXY = { x: e.x, y: e.y }; if (e.button !== 0) { From dfb9c535010aff0327f7f47f0a1c9538d1c5fbb0 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 10 Jan 2024 17:31:32 +0800 Subject: [PATCH 02/63] docs: update changlog of rush --- ...ousedown-not-stopPropagation_2024-01-10-09-31.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/892-refactor-mousedown-not-stopPropagation_2024-01-10-09-31.json diff --git a/common/changes/@visactor/vtable/892-refactor-mousedown-not-stopPropagation_2024-01-10-09-31.json b/common/changes/@visactor/vtable/892-refactor-mousedown-not-stopPropagation_2024-01-10-09-31.json new file mode 100644 index 000000000..24b4c33a2 --- /dev/null +++ b/common/changes/@visactor/vtable/892-refactor-mousedown-not-stopPropagation_2024-01-10-09-31.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "refactor: vtable not stop event bubble #892", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 5b994b41da28912c714f0c8bd26815f2e30d8bd2 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 31 Jan 2024 20:04:39 +0800 Subject: [PATCH 03/63] feat: add filter data config #607 --- .../examples/list-analysis/list-filter.ts | 361 ++++++++++++++++++ packages/vtable/examples/menu.ts | 9 + packages/vtable/src/ListTable.ts | 42 +- packages/vtable/src/PivotTable.ts | 8 +- packages/vtable/src/core/BaseTable.ts | 10 +- packages/vtable/src/core/tableHelper.ts | 20 +- packages/vtable/src/data/CachedDataSource.ts | 26 +- packages/vtable/src/data/DataSource.ts | 73 +++- packages/vtable/src/data/FilterDataSource.ts | 220 ----------- .../vtable/src/dataset/dataset-pivot-table.ts | 6 +- packages/vtable/src/dataset/dataset.ts | 19 +- .../vtable/src/layout/simple-header-layout.ts | 8 - packages/vtable/src/state/sort/index.ts | 12 +- packages/vtable/src/state/state.ts | 4 +- .../src/tools/get-data-path/create-dataset.ts | 6 +- packages/vtable/src/ts-types/base-table.ts | 12 +- .../src/ts-types/list-table/layout-map/api.ts | 1 - packages/vtable/src/ts-types/new-data-set.ts | 34 +- packages/vtable/src/ts-types/table-engine.ts | 23 +- 19 files changed, 560 insertions(+), 334 deletions(-) create mode 100644 packages/vtable/examples/list-analysis/list-filter.ts delete mode 100644 packages/vtable/src/data/FilterDataSource.ts diff --git a/packages/vtable/examples/list-analysis/list-filter.ts b/packages/vtable/examples/list-analysis/list-filter.ts new file mode 100644 index 000000000..ec3c45fc7 --- /dev/null +++ b/packages/vtable/examples/list-analysis/list-filter.ts @@ -0,0 +1,361 @@ +import * as VTable from '../../src'; +const CONTAINER_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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing' + })); +}; + +export function createTable() { + const records = generatePersons(1000000); + const columns: VTable.ColumnsDefine = [ + { + field: '', + title: '行号', + width: 80, + fieldFormat(data, col, row, table) { + return row - 1; + } + }, + { + field: 'id', + title: 'ID', + width: '1%', + minWidth: 200, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + } + ]; + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records, + dataConfig: { + filterRules: [ + { + filterFunc: (record: Record) => { + return record.id % 2 === 0; + } + } + ] + }, + columns, + tooltip: { + isShowOverflowTextTooltip: true + }, + frozenColCount: 1, + bottomFrozenRowCount: 2, + rightFrozenColCount: 2, + overscrollBehavior: 'none', + autoWrapText: true, + heightMode: 'autoHeight', + dragHeaderMode: 'all', + keyboardOptions: { + pasteValueToCell: true + }, + eventOptions: { + preventDefaultContextMenu: false + }, + pagination: { + perPageCount: 100, + currentPage: 4555 + } + // widthMode: 'adaptive' + }; + const tableInstance = new VTable.ListTable(option); + setTimeout(() => { + console.log(tableInstance.rowCount); + tableInstance.updateFilterRules([ + { + filterKey: 'sex', + filteredValues: ['boy'] + }, + { + filterFunc: (record: Record) => { + return record.id % 3 === 0; + } + } + ]); + }, 3000); + window.tableInstance = tableInstance; + tableInstance.on('change_cell_value', arg => { + console.log(arg); + }); +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index f3a635512..f8dc8aa0b 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -97,6 +97,15 @@ export const menus = [ } ] }, + { + menu: '基本表格分析', + children: [ + { + path: 'list-analysis', + name: 'list-filter' + } + ] + }, { menu: '透视表', children: [ diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 4891f77de..0723b7015 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -7,6 +7,7 @@ import type { FieldDef, FieldFormat, FieldKeyDef, + FilterRules, IPagination, ListTableAPI, ListTableConstructorOptions, @@ -61,6 +62,7 @@ export class ListTable extends BaseTable implements ListTableAPI { //分页配置 this.pagination = options.pagination; internalProps.sortState = options.sortState; + internalProps.dataConfig = cloneDeep(options.dataConfig ?? {}); internalProps.columns = options.columns ? cloneDeep(options.columns) : options.header @@ -317,6 +319,7 @@ export class ListTable extends BaseTable implements ListTableAPI { super.updateOption(options); //分页配置 this.pagination = options.pagination; + internalProps.dataConfig = cloneDeep(options.dataConfig ?? {}); //更新protectedSpace this.showHeader = options.showHeader ?? true; internalProps.columns = options.columns @@ -757,20 +760,14 @@ export class ListTable extends BaseTable implements ListTableAPI { } let order: any; let field: any; - let fieldKey: any; if (Array.isArray(this.internalProps.sortState)) { - ({ order, field, fieldKey } = this.internalProps.sortState?.[0]); + ({ order, field } = this.internalProps.sortState?.[0]); } else { - ({ order, field, fieldKey } = this.internalProps.sortState as SortState); + ({ order, field } = this.internalProps.sortState as SortState); } if (field && executeSort) { - const sortFunc = this._getSortFuncFromHeaderOption(this.internalProps.columns, field, fieldKey); - let hd; - if (fieldKey) { - hd = this.internalProps.layoutMap.headerObjects.find((col: any) => col && col.fieldKey === fieldKey); - } else { - hd = this.internalProps.layoutMap.headerObjects.find((col: any) => col && col.field === field); - } + const sortFunc = this._getSortFuncFromHeaderOption(this.internalProps.columns, field); + const hd = this.internalProps.layoutMap.headerObjects.find((col: any) => col && col.field === field); if (hd?.define?.sort) { this.dataSource.sort(hd.field, order, sortFunc); @@ -781,6 +778,17 @@ export class ListTable extends BaseTable implements ListTableAPI { } this.stateManager.updateSortState(sortState as SortState); } + updateFilterRules(filterRules: FilterRules) { + this.scenegraph.clearCells(); + this.internalProps.dataConfig.filterRules = filterRules; + this.dataSource.updateFilterRules(filterRules); + this.refreshRowColCount(); + this.headerStyleCache = new Map(); + this.bodyStyleCache = new Map(); + this.bodyBottomStyleCache = new Map(); + this.scenegraph.createSceneGraph(); + this.renderAsync(); + } /** 获取某个字段下checkbox 全部数据的选中状态 顺序对应原始传入数据records 不是对应表格展示row的状态值 */ getCheckboxState(field?: string | number) { if (this.stateManager.checkedState.length < this.rowCount - this.columnHeaderLevelCount) { @@ -825,24 +833,18 @@ export class ListTable extends BaseTable implements ListTableAPI { if ((this as any).sortState) { let order: any; let field: any; - let fieldKey: any; if (Array.isArray((this as any).sortState)) { if ((this as any).sortState.length !== 0) { - ({ order, field, fieldKey } = (this as any).sortState?.[0]); + ({ order, field } = (this as any).sortState?.[0]); } } else { - ({ order, field, fieldKey } = (this as any).sortState as SortState); + ({ order, field } = (this as any).sortState as SortState); } // 根据sort规则进行排序 if (order && field && order !== 'normal') { - const sortFunc = this._getSortFuncFromHeaderOption(undefined, field, fieldKey); + const sortFunc = this._getSortFuncFromHeaderOption(undefined, field); // 如果sort传入的信息不能生成正确的sortFunc,直接更新表格,避免首次加载无法正常显示内容 - let hd; - if (fieldKey) { - hd = this.internalProps.layoutMap.headerObjects.find((col: any) => col && col.fieldKey === fieldKey); - } else { - hd = this.internalProps.layoutMap.headerObjects.find((col: any) => col && col.field === field); - } + const hd = this.internalProps.layoutMap.headerObjects.find((col: any) => col && col.field === field); // hd?.define?.sort && //如果这里也判断 那想要利用sortState来排序 但不显示排序图标就实现不了 this.dataSource.sort(hd.field, order, sortFunc ?? defaultOrderFn); } diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 5b850af56..a93b6e7fe 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -84,9 +84,11 @@ export class PivotTable extends BaseTable implements PivotTableAPI { this.internalProps.columnResizeType = options.columnResizeType ?? 'column'; this.internalProps.dataConfig = cloneDeep(options.dataConfig); - this.internalProps.enableDataAnalysis = options.enableDataAnalysis; + // this.internalProps.enableDataAnalysis = options.enableDataAnalysis; if (!options.rowTree && !options.columnTree) { this.internalProps.enableDataAnalysis = true; + } else { + this.internalProps.enableDataAnalysis = false; } const records = this.internalProps.records; if (this.internalProps.enableDataAnalysis && (options.rows || options.columns)) { @@ -226,9 +228,11 @@ export class PivotTable extends BaseTable implements PivotTableAPI { // 更新protectedSpace internalProps.columnResizeType = options.columnResizeType ?? 'column'; internalProps.dataConfig = cloneDeep(options.dataConfig); - internalProps.enableDataAnalysis = options.enableDataAnalysis; + // internalProps.enableDataAnalysis = options.enableDataAnalysis; if (!options.rowTree && !options.columnTree) { internalProps.enableDataAnalysis = true; + } else { + internalProps.enableDataAnalysis = false; } //维护tree树形结构的展开状态 if ( diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 29d219cff..5ca9f2440 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -2568,9 +2568,6 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { getHeaderField(col: number, row: number): FieldDef { return this.internalProps.layoutMap.getHeaderField(col, row); } - getHeaderFieldKey(col: number, row: number): FieldDef { - return this.internalProps.layoutMap.getHeaderFieldKey(col, row); - } /** * 根据行列号获取配置 * @param {number} col column index. @@ -2659,12 +2656,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { */ _getHeaderCellBySortState(sortState: SortState): CellAddress | undefined { const { layoutMap } = this.internalProps; - let hd; - if (sortState.fieldKey) { - hd = layoutMap.headerObjects.find((col: any) => col && col.fieldKey === sortState.fieldKey); - } else { - hd = layoutMap.headerObjects.find((col: any) => col && col.field === sortState.field); - } + const hd = layoutMap.headerObjects.find((col: any) => col && col.field === sortState.field); if (hd) { const headercell = layoutMap.getHeaderCellAdressById(hd.id as number); return headercell; diff --git a/packages/vtable/src/core/tableHelper.ts b/packages/vtable/src/core/tableHelper.ts index a5e126783..02c91e99c 100644 --- a/packages/vtable/src/core/tableHelper.ts +++ b/packages/vtable/src/core/tableHelper.ts @@ -5,7 +5,7 @@ import { parseFont } from '../scenegraph/utils/font'; import { getQuadProps } from '../scenegraph/utils/padding'; import { Rect } from '../tools/Rect'; import * as calc from '../tools/calc'; -import type { FullExtendStyle, SortState } from '../ts-types'; +import type { FullExtendStyle, ListTableAPI, SortState } from '../ts-types'; import type { BaseTableAPI } from '../ts-types/base-table'; import { defaultOrderFn } from '../tools/util'; import type { ListTable } from '../ListTable'; @@ -51,12 +51,13 @@ export function _dealWithUpdateDataSource(table: BaseTableAPI, fn: (table: BaseT ]; } /** @private */ -export function _setRecords(table: BaseTableAPI, records: any[] = []): void { +export function _setRecords(table: ListTableAPI, records: any[] = []): void { _dealWithUpdateDataSource(table, () => { const data = records; table.internalProps.records = records; const newDataSource = (table.internalProps.dataSource = CachedDataSource.ofArray( data, + table.internalProps.dataConfig, table.pagination, (table.options as any).hierarchyExpandLevel ?? (table._hasHierarchyTreeHeader?.() ? 1 : undefined) )); @@ -322,24 +323,19 @@ export function sortRecords(table: ListTable) { if ((table as any).sortState) { let order: any; let field: any; - let fieldKey: any; if (Array.isArray((table as any).sortState)) { if ((table as any).sortState.length !== 0) { - ({ order, field, fieldKey } = (table as any).sortState?.[0]); + ({ order, field } = (table as any).sortState?.[0]); } } else { - ({ order, field, fieldKey } = (table as any).sortState as SortState); + ({ order, field } = (table as any).sortState as SortState); } // 根据sort规则进行排序 if (order && field && order !== 'normal') { - const sortFunc = table._getSortFuncFromHeaderOption(undefined, field, fieldKey); + const sortFunc = table._getSortFuncFromHeaderOption(undefined, field); // 如果sort传入的信息不能生成正确的sortFunc,直接更新表格,避免首次加载无法正常显示内容 - let hd; - if (fieldKey) { - hd = table.internalProps.layoutMap.headerObjects.find((col: any) => col && col.fieldKey === fieldKey); - } else { - hd = table.internalProps.layoutMap.headerObjects.find((col: any) => col && col.field === field); - } + const hd = table.internalProps.layoutMap.headerObjects.find((col: any) => col && col.field === field); + // hd?.define?.sort && //如果这里也判断 那想要利用sortState来排序 但不显示排序图标就实现不了 table.dataSource.sort(hd.field, order, sortFunc ?? defaultOrderFn); } diff --git a/packages/vtable/src/data/CachedDataSource.ts b/packages/vtable/src/data/CachedDataSource.ts index 1fa6e60af..eb5bf669f 100644 --- a/packages/vtable/src/data/CachedDataSource.ts +++ b/packages/vtable/src/data/CachedDataSource.ts @@ -1,5 +1,12 @@ import { getValueFromDeepArray } from '../tools/util'; -import type { FieldData, FieldDef, IPagination, MaybePromise, MaybePromiseOrUndefined } from '../ts-types'; +import type { + FieldData, + FieldDef, + IListTableDataConfig, + IPagination, + MaybePromise, + MaybePromiseOrUndefined +} from '../ts-types'; import type { BaseTableAPI } from '../ts-types/base-table'; import type { DataSourceParam } from './DataSource'; import { DataSource } from './DataSource'; @@ -33,7 +40,12 @@ export class CachedDataSource extends DataSource { static get EVENT_TYPE(): typeof DataSource.EVENT_TYPE { return DataSource.EVENT_TYPE; } - static ofArray(array: any[], pagination?: IPagination, hierarchyExpandLevel?: number): CachedDataSource { + static ofArray( + array: any[], + dataConfig?: IListTableDataConfig, + pagination?: IPagination, + hierarchyExpandLevel?: number + ): CachedDataSource { return new CachedDataSource( { get: (index: number): any => { @@ -45,12 +57,18 @@ export class CachedDataSource extends DataSource { length: array.length, source: array }, + dataConfig, pagination, hierarchyExpandLevel ); } - constructor(opt?: DataSourceParam, pagination?: IPagination, hierarchyExpandLevel?: number) { - super(opt, pagination, hierarchyExpandLevel); + constructor( + opt?: DataSourceParam, + dataConfig?: IListTableDataConfig, + pagination?: IPagination, + hierarchyExpandLevel?: number + ) { + super(opt, dataConfig, pagination, hierarchyExpandLevel); this._recordCache = {}; this._fieldCache = {}; } diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index be266a00d..4f88da3e8 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -5,6 +5,8 @@ import type { FieldData, FieldDef, FieldFormat, + FilterRules, + IListTableDataConfig, IPagination, MaybePromiseOrCallOrUndefined, MaybePromiseOrUndefined, @@ -126,11 +128,13 @@ export interface ISortedMapItem { } export class DataSource extends EventTarget implements DataSourceAPI { + dataConfig: IListTableDataConfig; + dataSourceObj: DataSourceParam | DataSource; private _get: (index: number | number[]) => any; /** 数据条目数 如果是树形结构的数据 则是第一层父节点的数量 */ private _sourceLength: number; - private readonly _source: any; + private _source: any; /** * 缓存按字段进行排序的结果 */ @@ -154,12 +158,18 @@ export class DataSource extends EventTarget implements DataSourceAPI { } protected treeDataHierarchyState: Map = new Map(); beforeChangedRecordsMap: Record[] = []; - constructor(obj?: DataSourceParam | DataSource, pagination?: IPagination, hierarchyExpandLevel?: number) { + constructor( + dataSourceObj?: DataSourceParam | DataSource, + dataConfig?: IListTableDataConfig, + pagination?: IPagination, + hierarchyExpandLevel?: number + ) { super(); - - this._get = obj?.get.bind(obj) || (undefined as any); - this._sourceLength = obj?.length || 0; - this._source = obj?.source ?? obj; + this.dataSourceObj = dataSourceObj; + this.dataConfig = dataConfig; + this._get = dataSourceObj?.get.bind(dataSourceObj) || (undefined as any); + this._source = this.filterRecords(dataSourceObj?.source ?? dataSourceObj); + this._sourceLength = this._source?.length || 0; this.sortedIndexMap = new Map(); this._currentPagerIndexedData = []; @@ -199,6 +209,20 @@ export class DataSource extends EventTarget implements DataSourceAPI { } this.updatePagerData(); } + + filterRecords(records: any[]) { + const filteredRecords = []; + if (this.dataConfig?.filterRules?.length >= 1) { + for (let i = 0, len = records.length; i < len; i++) { + const record = records[i]; + if (this.filterRecord(record)) { + filteredRecords.push(record); + } + } + return filteredRecords; + } + return records; + } /** * 初始化子节点的层次信息 * @param indexKey 父节点的indexKey 即currentLevel-1的节点 @@ -741,6 +765,40 @@ export class DataSource extends EventTarget implements DataSourceAPI { this.updatePagerData(); this.fireListeners(EVENT_TYPE.CHANGE_ORDER, null); } + + private filterRecord(record: any) { + let isReserved = true; + for (let i = 0; i < this.dataConfig.filterRules.length; i++) { + const filterRule = this.dataConfig?.filterRules[i]; + if (filterRule.filterKey) { + const filterValue = record[filterRule.filterKey]; + if (filterRule.filteredValues.indexOf(filterValue) === -1) { + isReserved = false; + break; + } + } else if (!filterRule.filterFunc?.(record)) { + isReserved = false; + break; + } + } + return isReserved; + } + + updateFilterRules(filterRules?: FilterRules): void { + this.dataConfig.filterRules = filterRules; + this._source = this.filterRecords(this.dataSourceObj?.source ?? this.dataSourceObj); + this._sourceLength = this._source?.length || 0; + // 初始化currentIndexedData 正常未排序。设置其状态 + this.currentIndexedData = Array.from({ length: this._sourceLength }, (_, i) => i); + if (this.userPagination) { + // 如果用户配置了分页 + this.updatePagerData(); + } else { + this.pagination.perPageCount = this._sourceLength; + this.pagination.totalCount = this._sourceLength; + this.updatePagerData(); + } + } /** * 当节点折叠或者展开时 将排序缓存清空(非当前排序规则的缓存) */ @@ -801,6 +859,9 @@ export class DataSource extends EventTarget implements DataSourceAPI { this.currentPagerIndexedData.length = 0; } protected getOriginalRecord(dataIndex: number | number[]): MaybePromiseOrUndefined { + if (this.dataConfig?.filterRules) { + return (this.source as Array)[dataIndex as number]; + } return getValue(this._get(dataIndex), (val: MaybePromiseOrUndefined) => { this.recordPromiseCallBack(dataIndex, val); }); diff --git a/packages/vtable/src/data/FilterDataSource.ts b/packages/vtable/src/data/FilterDataSource.ts deleted file mode 100644 index c18286246..000000000 --- a/packages/vtable/src/data/FilterDataSource.ts +++ /dev/null @@ -1,220 +0,0 @@ -// import type { FieldDef, MaybePromise, MaybePromiseOrUndefined } from "../ts-types"; -// import { each, isPromise } from "../tools/utils"; -// import { DataSource } from "./DataSource"; -// import { EventHandler } from "../tools/EventHandler"; - -// /** @private */ -// type Filter = (record: T | undefined) => boolean; - -// /** @private */ -// class DataSourceIterator { -// _dataSource: DataSource; -// _curIndex: number; -// _data: MaybePromiseOrUndefined[]; -// constructor(dataSource: DataSource) { -// this._dataSource = dataSource; -// this._curIndex = -1; -// this._data = []; -// } -// hasNext(): boolean { -// const next = this._curIndex + 1; -// return this._dataSource.length > next; -// } -// next(): MaybePromiseOrUndefined { -// const next = this._curIndex + 1; -// const data = this._getIndexData(next); -// this._curIndex = next; -// return data; -// } -// movePrev(): void { -// this._curIndex--; -// } -// _getIndexData(index: number, nest?: boolean): MaybePromiseOrUndefined { -// const dataSource = this._dataSource; -// const data = this._data; -// if (index < data.length) { -// return data[index]; -// } - -// if (dataSource.length <= index) { -// return undefined; -// } -// const record = this._dataSource.get(index); -// data[index] = record; -// if (isPromise(record)) { -// record.then((val) => { -// data[index] = val; -// }); -// if (!nest) { -// for (let i = 1; i <= 100; i++) { -// this._getIndexData(index + i, true); -// } -// } -// } -// return record; -// } -// } -// /** @private */ -// class FilterData { -// _owner: FilterDataSource; -// _dataSourceItr: DataSourceIterator; -// _filter: Filter; -// _filterdList: (T | undefined)[]; -// _queues: (Promise | null)[]; -// _cancel = false; -// constructor( -// dc: FilterDataSource, -// original: DataSource, -// filter: Filter -// ) { -// this._owner = dc; -// this._dataSourceItr = new DataSourceIterator(original); -// this._filter = filter; -// this._filterdList = []; -// this._queues = []; -// } -// get(index: number): MaybePromiseOrUndefined { -// if (this._cancel) { -// return undefined; -// } -// const filterdList = this._filterdList; -// if (index < filterdList.length) { -// return filterdList[index]; -// } -// const queues = this._queues; -// const indexQueue = queues[index]; -// if (indexQueue) { -// return indexQueue; -// } -// return queues[index] || this._findIndex(index); -// } -// cancel(): void { -// this._cancel = true; -// } -// _findIndex(index: number): MaybePromiseOrUndefined { -// if (window.Promise) { -// const timeout = Date.now() + 100; -// let count = 0; -// return this._findIndexWithTimeout(index, () => { -// count++; -// if (count >= 100) { -// count = 0; -// return timeout < Date.now(); -// } -// return false; -// }); -// } -// return this._findIndexWithTimeout(index, () => false); -// } -// _findIndexWithTimeout( -// index: number, -// testTimeout: () => boolean -// ): MaybePromiseOrUndefined { -// const filterdList = this._filterdList; -// const filter = this._filter; -// const dataSourceItr = this._dataSourceItr; - -// const queues = this._queues; - -// while (dataSourceItr.hasNext()) { -// if (this._cancel) { -// return undefined; -// } -// const record = dataSourceItr.next(); -// if (isPromise(record)) { -// dataSourceItr.movePrev(); -// const queue = record.then((_value) => { -// queues[index] = null; -// return this.get(index); -// }); -// queues[index] = queue; -// return queue; -// } -// if (filter(record)) { -// filterdList.push(record); -// if (index < filterdList.length) { -// return filterdList[index]; -// } -// } -// if (testTimeout()) { -// const promise = new Promise((resolve) => { -// setTimeout(() => { -// resolve(); -// }, 300); -// }); -// const queue = promise.then(() => { -// queues[index] = null; -// return this.get(index); -// }); -// queues[index] = queue; -// return queue; -// } -// } -// const dc = this._owner; -// dc.length = filterdList.length; -// return undefined; -// } -// } - -// /** -// * table data source for filter -// * -// * @classdesc VTable.data.FilterDataSource -// * @memberof VTable.data -// */ -// export class FilterDataSource extends DataSource { -// private _dataSource: DataSource; -// private _handler: EventHandler; -// private _filterData: FilterData | null = null; -// static get EVENT_TYPE(): typeof DataSource.EVENT_TYPE { -// return DataSource.EVENT_TYPE; -// } -// constructor(dataSource: DataSource, filter: Filter) { -// super(dataSource); -// this._dataSource = dataSource; -// this.filter = filter; -// const handler = (this._handler = new EventHandler()); -// handler.on(dataSource, DataSource.EVENT_TYPE.UPDATED_ORDER, () => { -// // reset -// // eslint-disable-next-line no-self-assign -// this.filter = this.filter; -// }); -// each(DataSource.EVENT_TYPE, (type) => { -// handler.on(dataSource, type, (...args) => -// this.fireListeners(type, ...args) -// ); -// }); -// } -// get filter(): Filter | null { -// return this._filterData?._filter || null; -// } -// set filter(filter: Filter | null) { -// if (this._filterData) { -// this._filterData.cancel(); -// } -// this._filterData = filter -// ? new FilterData(this, this._dataSource, filter) -// : null; -// this.length = this._dataSource.length; -// } -// protected getOriginal(index: number): MaybePromiseOrUndefined { -// if (!this._filterData) { -// return super.getOriginal(index); -// } -// return this._filterData.get(index); -// } -// sort(field: FieldDef, order: "desc" | "asc",orderFn: (v1: T, v2: T, order: string) => -1 | 0 | 1): void { -// this._dataSource.sort(field, order,orderFn); -// } -// -// get source(): any { -// return this._dataSource.source; -// } -// get dataSource(): DataSource { -// return this._dataSource; -// } -// release(): void { -// this._handler.release?.(); -// super.release?.(); -// } -// } diff --git a/packages/vtable/src/dataset/dataset-pivot-table.ts b/packages/vtable/src/dataset/dataset-pivot-table.ts index bf3b6d6db..5ba629cb0 100644 --- a/packages/vtable/src/dataset/dataset-pivot-table.ts +++ b/packages/vtable/src/dataset/dataset-pivot-table.ts @@ -1,6 +1,6 @@ import type { FilterRules, - IDataConfig, + IPivotTableDataConfig, SortRule, AggregationRules, AggregationRule, @@ -35,7 +35,7 @@ export class DatasetForPivotTable { /** * 用户配置 */ - dataConfig: IDataConfig; + dataConfig: IPivotTableDataConfig; /** * 明细数据 */ @@ -94,7 +94,7 @@ export class DatasetForPivotTable { columns: string[]; indicatorKeys: string[]; constructor( - dataConfig: IDataConfig, + dataConfig: IPivotTableDataConfig, rows: string[], columns: string[], indicators: string[], diff --git a/packages/vtable/src/dataset/dataset.ts b/packages/vtable/src/dataset/dataset.ts index 2882bcc91..7cb0e8b05 100644 --- a/packages/vtable/src/dataset/dataset.ts +++ b/packages/vtable/src/dataset/dataset.ts @@ -1,7 +1,7 @@ import { isArray, isValid } from '@visactor/vutils'; import type { FilterRules, - IDataConfig, + IPivotTableDataConfig, SortRule, AggregationRules, AggregationRule, @@ -18,7 +18,8 @@ import type { IHeaderTreeDefine, CollectValueBy, CollectedValue, - IIndicator + IIndicator, + IPivotChartDataConfig } from '../ts-types'; import { AggregationType, SortType } from '../ts-types'; import type { Aggregator } from './statistics-helper'; @@ -41,7 +42,7 @@ export class Dataset { /** * 用户配置 */ - dataConfig: IDataConfig; + dataConfig: IPivotTableDataConfig | IPivotChartDataConfig; // /** // * 分页配置 // */ @@ -127,7 +128,7 @@ export class Dataset { // 记录用户传入的汇总数据 totalRecordsTree: Record> = {}; constructor( - dataConfig: IDataConfig, + dataConfig: IPivotTableDataConfig | IPivotChartDataConfig, // pagination: IPagination, rows: string[], columns: string[], @@ -160,7 +161,7 @@ export class Dataset { this.colSubTotalLabel = this.totals?.column?.subTotalLabel ?? '小计'; this.rowGrandTotalLabel = this.totals?.row?.grandTotalLabel ?? '总计'; this.rowSubTotalLabel = this.totals?.row?.subTotalLabel ?? '小计'; - this.collectValuesBy = this.dataConfig?.collectValuesBy; + this.collectValuesBy = (this.dataConfig as IPivotChartDataConfig)?.collectValuesBy; this.needSplitPositiveAndNegative = needSplitPositiveAndNegative ?? false; this.rowsIsTotal = new Array(this.rows?.length ?? 0).fill(false); this.colsIsTotal = new Array(this.columns?.length ?? 0).fill(false); @@ -280,7 +281,7 @@ export class Dataset { const t8 = typeof window !== 'undefined' ? window.performance.now() : 0; console.log('TreeToArr:', t8 - t7); - if (this.dataConfig?.isPivotChart) { + if ((this.dataConfig as IPivotChartDataConfig)?.isPivotChart) { // 处理PivotChart双轴图0值对齐 // this.dealWithZeroAlign(); @@ -754,7 +755,7 @@ export class Dataset { this.processCollectedValuesWithSumBy(); this.processCollectedValuesWithSortBy(); - if (this.dataConfig?.isPivotChart) { + if ((this.dataConfig as IPivotChartDataConfig)?.isPivotChart) { // 处理PivotChart双轴图0值对齐 // this.dealWithZeroAlign(); // 记录PivotChart维度对应的数据 @@ -1449,10 +1450,10 @@ export class Dataset { private cacheDeminsionCollectedValues() { for (const key in this.collectValuesBy) { if (this.collectValuesBy[key].type === 'xField' || this.collectValuesBy[key].type === 'yField') { - if (this.dataConfig.dimensionSortArray) { + if ((this.dataConfig as IPivotChartDataConfig).dimensionSortArray) { this.cacheCollectedValues[key] = arraySortByAnotherArray( this.collectedValues[key] as unknown as string[], - this.dataConfig.dimensionSortArray + (this.dataConfig as IPivotChartDataConfig).dimensionSortArray ) as unknown as Record; } else { this.cacheCollectedValues[key] = this.collectedValues[key]; diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index b9336bf0d..3746c269c 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -401,13 +401,6 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { const id = this.getCellId(col, row); return this._headerObjectMap[id as number]!; } - getHeaderFieldKey(col: number, row: number) { - const id = this.getCellId(col, row); - return ( - this._headerObjectMap[id as number]?.fieldKey || - (this.transpose ? this._columns[row]?.fieldKey : this._columns[col]?.fieldKey) - ); - } getHeaderField(col: number, row: number) { const id = this.getCellId(col, row); return ( @@ -658,7 +651,6 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { // captionIcon, headerIcon: hd.headerIcon, field: (hd as ColumnDefine).field, - fieldKey: (hd as ColumnDefine)?.fieldKey, fieldFormat: (hd as ColumnDefine).fieldFormat, style: hd.headerStyle, headerType: hd.headerType ?? 'text', diff --git a/packages/vtable/src/state/sort/index.ts b/packages/vtable/src/state/sort/index.ts index 08cc052db..6fdc7a3f1 100644 --- a/packages/vtable/src/state/sort/index.ts +++ b/packages/vtable/src/state/sort/index.ts @@ -42,7 +42,6 @@ export function dealSort(col: number, row: number, table: ListTableAPI) { } else if (headerC?.sort) { //如果当前表头设置了sort 则 转变sort的状态 tableState = { - fieldKey: table.getHeaderFieldKey(col, row), field: table.getHeaderField(col, row), order: 'asc' }; @@ -50,7 +49,6 @@ export function dealSort(col: number, row: number, table: ListTableAPI) { //当前排序规则是该表头field 且仅为显示showSort无sort 什么也不做 } else { tableState = { - fieldKey: table.getHeaderFieldKey(col, row), field: table.getHeaderField(col, row), order: 'normal' }; @@ -76,14 +74,8 @@ export function dealSort(col: number, row: number, table: ListTableAPI) { } function executeSort(newState: SortState, table: BaseTableAPI, headerDefine: HeaderDefine): void { - let hd; - if (newState.fieldKey) { - hd = table.internalProps.layoutMap.headerObjects.find( - (col: HeaderData) => col && col.fieldKey === newState.fieldKey - ); - } else { - hd = table.internalProps.layoutMap.headerObjects.find((col: HeaderData) => col && col.field === newState.field); - } + const hd = table.internalProps.layoutMap.headerObjects.find((col: HeaderData) => col && col.field === newState.field); + if (!hd) { return; } diff --git a/packages/vtable/src/state/state.ts b/packages/vtable/src/state/state.ts index 8f8d8ddde..3c82ec7cb 100644 --- a/packages/vtable/src/state/state.ts +++ b/packages/vtable/src/state/state.ts @@ -108,7 +108,7 @@ export class StateManager { col: number; row: number; field?: string; - fieldKey?: string; + // fieldKey?: string; order: SortOrder; icon?: Icon; }; @@ -406,7 +406,7 @@ export class StateManager { setSortState(sortState: SortState) { this.sort.field = sortState?.field as string; - this.sort.fieldKey = sortState?.fieldKey as string; + // this.sort.fieldKey = sortState?.fieldKey as string; this.sort.order = sortState?.order; // // 这里有一个问题,目前sortState中一般只传入了fieldKey,但是getCellRangeByField需要field // const range = this.table.getCellRangeByField(this.sort.field, 0); diff --git a/packages/vtable/src/tools/get-data-path/create-dataset.ts b/packages/vtable/src/tools/get-data-path/create-dataset.ts index 62563ab31..86693a4fd 100644 --- a/packages/vtable/src/tools/get-data-path/create-dataset.ts +++ b/packages/vtable/src/tools/get-data-path/create-dataset.ts @@ -3,15 +3,15 @@ import type { AggregationRule, AggregationRules, CollectValueBy, - IDataConfig, IIndicator, - PivotChartConstructorOptions + PivotChartConstructorOptions, + IPivotChartDataConfig } from '../../ts-types'; import { AggregationType } from '../../ts-types'; import type { IChartColumnIndicator } from '../../ts-types/pivot-table/indicator/chart-indicator'; export function createDataset(options: PivotChartConstructorOptions) { - const dataConfig: IDataConfig = { isPivotChart: true }; + const dataConfig: IPivotChartDataConfig = { isPivotChart: true }; const rowKeys = options.rows?.reduce((keys, rowObj) => { diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index e8afc5e23..9ab7cc9c1 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -32,7 +32,7 @@ import type { HeaderValues, HeightModeDef, HierarchyState, - IDataConfig, + IPivotTableDataConfig, IPagination, ITableThemeDefine, SortState, @@ -46,7 +46,9 @@ import type { CustomMerge, IColumnDimension, IRowDimension, - TableEventOptions + TableEventOptions, + IPivotChartDataConfig, + IListTableDataConfig } from '.'; import type { TooltipOptions } from './tooltip'; import type { IWrapTextGraphicAttribute } from '../scenegraph/graphic/text'; @@ -558,7 +560,6 @@ export interface BaseTableAPI { getRecordStartRowByRecordIndex: (index: number) => number; getHeaderField: (col: number, row: number) => any | undefined; - getHeaderFieldKey: (col: number, row: number) => any | undefined; _getHeaderCellBySortState: (sortState: SortState) => CellAddress | undefined; getHeaderDefine: (col: number, row: number) => ColumnDefine; @@ -691,6 +692,7 @@ export interface BaseTableAPI { export interface ListTableProtected extends IBaseTableProtected { /** 表格数据 */ records: any[] | null; + dataConfig?: IListTableDataConfig; columns: ColumnsDefine; layoutMap: SimpleHeaderLayoutMap; } @@ -699,7 +701,7 @@ export interface PivotTableProtected extends IBaseTableProtected { /** 表格数据 */ records: any[] | null; layoutMap: PivotHeaderLayoutMap; - dataConfig?: IDataConfig; + dataConfig?: IPivotTableDataConfig; /** * 透视表是否开启数据分析 * 如果传入数据是明细数据需要聚合分析则开启 @@ -722,7 +724,7 @@ export interface PivotChartProtected extends IBaseTableProtected { /** 表格数据 */ records: any[] | Record; layoutMap: PivotHeaderLayoutMap; - dataConfig?: IDataConfig; + dataConfig?: IPivotChartDataConfig; columnTree?: IHeaderTreeDefine[]; /** 行表头维度结构 */ rowTree?: IHeaderTreeDefine[]; diff --git a/packages/vtable/src/ts-types/list-table/layout-map/api.ts b/packages/vtable/src/ts-types/list-table/layout-map/api.ts index fb8368737..8143e03bb 100644 --- a/packages/vtable/src/ts-types/list-table/layout-map/api.ts +++ b/packages/vtable/src/ts-types/list-table/layout-map/api.ts @@ -53,7 +53,6 @@ export interface HeaderData extends WidthData { icons?: (string | ColumnIconOption)[] | ((args: CellInfo) => (string | ColumnIconOption)[]); field: FieldDef; - fieldKey?: FieldKeyDef; fieldFormat?: FieldFormat; style?: HeaderStyleOption | ColumnStyle | null | undefined; headerType: 'text' | 'link' | 'image' | 'video' | 'checkbox'; // headerType.BaseHeader; diff --git a/packages/vtable/src/ts-types/new-data-set.ts b/packages/vtable/src/ts-types/new-data-set.ts index 3ee6833c6..01d112552 100644 --- a/packages/vtable/src/ts-types/new-data-set.ts +++ b/packages/vtable/src/ts-types/new-data-set.ts @@ -1,3 +1,4 @@ +import type { Either } from '../tools/helper'; import type { SortOrder } from './common'; //#region 总计小计 @@ -119,12 +120,14 @@ export type SortRules = SortRule[]; //#endregion 排序规则 //#region 过滤规则 -export interface FilterRule { +export interface FilterFuncRule { + filterFunc?: (row: Record) => boolean; +} +export interface FilterValueRule { filterKey?: string; filteredValues?: unknown[]; - filterFunc?: (row: Record) => boolean; } -export type FilterRules = FilterRule[]; +export type FilterRules = Either[]; //#endregion 过滤规则 //#region 聚合规则 @@ -169,9 +172,19 @@ export interface DerivedFieldRule { } export type DerivedFieldRules = DerivedFieldRule[]; /** - * 数据处理配置 + * 基本表数据处理配置 + */ +export interface IListTableDataConfig { + // aggregationRules?: AggregationRules; //按照行列维度聚合值计算规则; + // sortRules?: SortRules; //排序规则; + filterRules?: FilterRules; //过滤规则; + // totals?: Totals; //小计或总计; + // derivedFieldRules?: DerivedFieldRules; +} +/** + * 透视表数据处理配置 */ -export interface IDataConfig { +export interface IPivotTableDataConfig { aggregationRules?: AggregationRules; //按照行列维度聚合值计算规则; sortRules?: SortRules; //排序规则; filterRules?: FilterRules; //过滤规则; @@ -181,17 +194,22 @@ export interface IDataConfig { */ mappingRules?: MappingRules; derivedFieldRules?: DerivedFieldRules; +} +/** + * 透视图数据处理配置 + */ +export interface IPivotChartDataConfig extends IPivotTableDataConfig { /** - * PivotChart专有 请忽略 + * PivotChart专有 */ collectValuesBy?: Record; /** - * PivotChart专有 请忽略 + * PivotChart专有 */ isPivotChart?: boolean; /** - * PivotChart专有 请忽略 + * PivotChart专有 */ dimensionSortArray?: string[]; } diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index 270995db3..adcf7053d 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -3,8 +3,8 @@ import type { SvgIcon } from './icon'; export type { HeaderData } from './list-table/layout-map/api'; export type LayoutObjectId = number | string; import type { Rect } from '../tools/Rect'; -import type { BaseTableAPI, BaseTableConstructorOptions } from './base-table'; -import type { IDataConfig } from './new-data-set'; +import type { BaseTableAPI, BaseTableConstructorOptions, ListTableProtected } from './base-table'; +import type { FilterRules, IListTableDataConfig, IPivotTableDataConfig } from './new-data-set'; import type { Either } from '../tools/helper'; import type { IChartIndicator, @@ -104,8 +104,6 @@ export interface DataSourceAPI { export interface SortState { /** 排序依据字段 */ field: FieldDef; - - fieldKey?: FieldKeyDef; /** 排序规则 */ order: SortOrder; } @@ -164,6 +162,8 @@ export interface ListTableConstructorOptions extends BaseTableConstructorOptions * 排序状态 */ sortState?: SortState | SortState[]; + /** 数据分析相关配置 enableDataAnalysis开启后该配置才会有效 */ + dataConfig?: IListTableDataConfig; /** 全局设置表头编辑器 */ headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); /** 全局设置编辑器 */ @@ -176,7 +176,9 @@ export interface ListTableAPI extends BaseTableAPI { options: ListTableConstructorOptions; editorManager: EditManeger; sortState: SortState[] | SortState | null; - // internalProps: ListTableProtected; + // /** 数据分析相关配置 */ + // dataConfig?: IListTableDataConfig; + internalProps: ListTableProtected; isListTable: () => true; isPivotTable: () => false; /** 设置单元格的value值,注意对应的是源数据的原始值,vtable实例records会做对应修改 */ @@ -200,6 +202,8 @@ export interface ListTableAPI extends BaseTableAPI { addRecord: (record: any, recordIndex?: number) => void; addRecords: (records: any[], recordIndex?: number) => void; deleteRecords: (recordIndexs: number[]) => void; + updateRecords: (records: any[], recordIndexs: number[]) => void; + updateFilterRules: (filterRules: FilterRules) => void; } export interface PivotTableConstructorOptions extends BaseTableConstructorOptions { /** @@ -265,13 +269,8 @@ export interface PivotTableConstructorOptions extends BaseTableConstructorOption rowHeaderTitle?: ITitleDefine; //#endregion /** 数据分析相关配置 enableDataAnalysis开启后该配置才会有效 */ - dataConfig?: IDataConfig; - /** - * 透视表是否开启数据分析 默认false - * 如果传入数据是明细数据需要聚合分析则开启 赋值为true - * 如传入数据是经过聚合好的为了提升性能这里设为false即可,同时呢需要传入自己组织好的行头树结构columnTree和rowTree - */ - enableDataAnalysis?: boolean; + dataConfig?: IPivotTableDataConfig; + /** 指标标题 用于显示到角头的值*/ indicatorTitle?: string; /** 分页配置 */ From fc0322177eb5de023ac3549047f56f004a04104c Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 31 Jan 2024 20:05:00 +0800 Subject: [PATCH 04/63] docs: update changlog of rush --- .../607-feature-filter-api_2024-01-31-12-05.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/607-feature-filter-api_2024-01-31-12-05.json diff --git a/common/changes/@visactor/vtable/607-feature-filter-api_2024-01-31-12-05.json b/common/changes/@visactor/vtable/607-feature-filter-api_2024-01-31-12-05.json new file mode 100644 index 000000000..c5391b829 --- /dev/null +++ b/common/changes/@visactor/vtable/607-feature-filter-api_2024-01-31-12-05.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: add filter data config #607\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From af0b2de9ee40d1c0da2f053946b6a99e9dcb55a2 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 1 Feb 2024 20:30:14 +0800 Subject: [PATCH 05/63] feat: filterRules handle sort state --- .../vtable/examples/list-analysis/list-filter.ts | 6 +++++- packages/vtable/src/ListTable.ts | 11 ++++++----- packages/vtable/src/data/DataSource.ts | 12 ++++++++++++ packages/vtable/src/ts-types/new-data-set.ts | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/vtable/examples/list-analysis/list-filter.ts b/packages/vtable/examples/list-analysis/list-filter.ts index ec3c45fc7..01ba51714 100644 --- a/packages/vtable/examples/list-analysis/list-filter.ts +++ b/packages/vtable/examples/list-analysis/list-filter.ts @@ -307,6 +307,10 @@ export function createTable() { const option: VTable.ListTableConstructorOptions = { container: document.getElementById(CONTAINER_ID), records, + sortState: { + field: 'id', + order: 'desc' + }, dataConfig: { filterRules: [ { @@ -335,7 +339,7 @@ export function createTable() { }, pagination: { perPageCount: 100, - currentPage: 4555 + currentPage: 0 } // widthMode: 'adaptive' }; diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 0723b7015..efb61181e 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -781,13 +781,14 @@ export class ListTable extends BaseTable implements ListTableAPI { updateFilterRules(filterRules: FilterRules) { this.scenegraph.clearCells(); this.internalProps.dataConfig.filterRules = filterRules; - this.dataSource.updateFilterRules(filterRules); + if (this.sortState) { + this.dataSource.updateFilterRulesForSorted(filterRules); + sortRecords(this); + } else { + this.dataSource.updateFilterRules(filterRules); + } this.refreshRowColCount(); - this.headerStyleCache = new Map(); - this.bodyStyleCache = new Map(); - this.bodyBottomStyleCache = new Map(); this.scenegraph.createSceneGraph(); - this.renderAsync(); } /** 获取某个字段下checkbox 全部数据的选中状态 顺序对应原始传入数据records 不是对应表格展示row的状态值 */ getCheckboxState(field?: string | number) { diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index 4f88da3e8..a0f4d7428 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -784,6 +784,18 @@ export class DataSource extends EventTarget implements DataSourceAPI { return isReserved; } + updateFilterRulesForSorted(filterRules?: FilterRules): void { + this.dataConfig.filterRules = filterRules; + this._source = this.filterRecords(this.dataSourceObj?.source ?? this.dataSourceObj); + this._sourceLength = this._source?.length || 0; + this.sortedIndexMap.clear(); + this.currentIndexedData = Array.from({ length: this._sourceLength }, (_, i) => i); + if (!this.userPagination) { + this.pagination.perPageCount = this._sourceLength; + this.pagination.totalCount = this._sourceLength; + } + } + updateFilterRules(filterRules?: FilterRules): void { this.dataConfig.filterRules = filterRules; this._source = this.filterRecords(this.dataSourceObj?.source ?? this.dataSourceObj); diff --git a/packages/vtable/src/ts-types/new-data-set.ts b/packages/vtable/src/ts-types/new-data-set.ts index 01d112552..3bfda55b5 100644 --- a/packages/vtable/src/ts-types/new-data-set.ts +++ b/packages/vtable/src/ts-types/new-data-set.ts @@ -176,7 +176,7 @@ export type DerivedFieldRules = DerivedFieldRule[]; */ export interface IListTableDataConfig { // aggregationRules?: AggregationRules; //按照行列维度聚合值计算规则; - // sortRules?: SortRules; //排序规则; + // sortRules?: SortTypeRule | SortByRule | SortFuncRule; //排序规则 不能简单的将sortState挪到这里 sort的规则在column中配置的; filterRules?: FilterRules; //过滤规则; // totals?: Totals; //小计或总计; // derivedFieldRules?: DerivedFieldRules; From 9d7cbddd6b7370599c95886fc6850ffb75c2404d Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 2 Feb 2024 18:09:14 +0800 Subject: [PATCH 06/63] refactor: remove dataconfig from listTable --- .../examples/list-analysis/list-filter.ts | 18 +++++++++--------- .../pivot-analysis/pivot-analysis-filter.ts | 1 - packages/vtable/src/ListTable.ts | 4 ++-- packages/vtable/src/ts-types/table-engine.ts | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/vtable/examples/list-analysis/list-filter.ts b/packages/vtable/examples/list-analysis/list-filter.ts index 01ba51714..1353ca118 100644 --- a/packages/vtable/examples/list-analysis/list-filter.ts +++ b/packages/vtable/examples/list-analysis/list-filter.ts @@ -311,15 +311,15 @@ export function createTable() { field: 'id', order: 'desc' }, - dataConfig: { - filterRules: [ - { - filterFunc: (record: Record) => { - return record.id % 2 === 0; - } - } - ] - }, + // dataConfig: { + // filterRules: [ + // { + // filterFunc: (record: Record) => { + // return record.id % 2 === 0; + // } + // } + // ] + // }, columns, tooltip: { isShowOverflowTextTooltip: true diff --git a/packages/vtable/examples/pivot-analysis/pivot-analysis-filter.ts b/packages/vtable/examples/pivot-analysis/pivot-analysis-filter.ts index 69e0a1307..fb48db0b5 100644 --- a/packages/vtable/examples/pivot-analysis/pivot-analysis-filter.ts +++ b/packages/vtable/examples/pivot-analysis/pivot-analysis-filter.ts @@ -27,7 +27,6 @@ export function createTable() { rows: ['province', 'city'], columns: ['category', 'sub_category'], indicators: ['sales', 'number'], - enableDataAnalysis: true, indicatorTitle: '指标名称', indicatorsAsCol: false, dataConfig: { diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index efb61181e..8e2aea21f 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -62,7 +62,7 @@ export class ListTable extends BaseTable implements ListTableAPI { //分页配置 this.pagination = options.pagination; internalProps.sortState = options.sortState; - internalProps.dataConfig = cloneDeep(options.dataConfig ?? {}); + internalProps.dataConfig = {}; //cloneDeep(options.dataConfig ?? {}); internalProps.columns = options.columns ? cloneDeep(options.columns) : options.header @@ -319,7 +319,7 @@ export class ListTable extends BaseTable implements ListTableAPI { super.updateOption(options); //分页配置 this.pagination = options.pagination; - internalProps.dataConfig = cloneDeep(options.dataConfig ?? {}); + internalProps.dataConfig = {}; // cloneDeep(options.dataConfig ?? {}); //更新protectedSpace this.showHeader = options.showHeader ?? true; internalProps.columns = options.columns diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index adcf7053d..26c424ae4 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -163,7 +163,7 @@ export interface ListTableConstructorOptions extends BaseTableConstructorOptions */ sortState?: SortState | SortState[]; /** 数据分析相关配置 enableDataAnalysis开启后该配置才会有效 */ - dataConfig?: IListTableDataConfig; + // dataConfig?: IListTableDataConfig; /** 全局设置表头编辑器 */ headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); /** 全局设置编辑器 */ From 6364c06f5a4809ed79a64102df871c2294d266ea Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Sun, 4 Feb 2024 19:46:50 +0800 Subject: [PATCH 07/63] feat: add aggregation for list table column --- .../list-analysis/list-aggregation.ts | 150 ++++++++++++++++++ packages/vtable/examples/menu.ts | 4 + packages/vtable/src/ListTable.ts | 23 ++- packages/vtable/src/PivotTable.ts | 2 +- packages/vtable/src/core/tableHelper.ts | 1 + packages/vtable/src/data/CachedDataSource.ts | 6 +- packages/vtable/src/data/DataSource.ts | 86 ++++++++-- packages/vtable/src/layout/layout-helper.ts | 35 ++++ .../vtable/src/layout/pivot-header-layout.ts | 10 +- .../vtable/src/layout/simple-header-layout.ts | 91 ++++++++++- ...{pivot-layout-helper.ts => tree-helper.ts} | 0 .../list-table/define/basic-define.ts | 6 +- .../src/ts-types/list-table/layout-map/api.ts | 8 +- packages/vtable/src/ts-types/new-data-set.ts | 7 + 14 files changed, 390 insertions(+), 39 deletions(-) create mode 100644 packages/vtable/examples/list-analysis/list-aggregation.ts create mode 100644 packages/vtable/src/layout/layout-helper.ts rename packages/vtable/src/layout/{pivot-layout-helper.ts => tree-helper.ts} (100%) diff --git a/packages/vtable/examples/list-analysis/list-aggregation.ts b/packages/vtable/examples/list-analysis/list-aggregation.ts new file mode 100644 index 000000000..45620d17f --- /dev/null +++ b/packages/vtable/examples/list-analysis/list-aggregation.ts @@ -0,0 +1,150 @@ +import * as VTable from '../../src'; +import { AggregationType } from '../../src/ts-types'; +const CONTAINER_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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + salary: Math.round(Math.random() * 10000) + })); +}; + +export function createTable() { + const records = generatePersons(30); + const columns: VTable.ColumnsDefine = [ + { + field: '', + title: '行号', + width: 80, + fieldFormat(data, col, row, table) { + return row - 1; + } + }, + { + field: 'id', + title: 'ID', + width: '1%', + minWidth: 200, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'salary', + title: 'salary', + width: 100, + aggregation: { + aggregationType: AggregationType.AVG + } + }, + { + field: 'salary', + title: 'salary', + width: 100, + aggregation: { + aggregationType: AggregationType.MAX, + showOnTop: true + } + } + ]; + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records, + sortState: { + field: 'id', + order: 'desc' + }, + // dataConfig: { + // filterRules: [ + // { + // filterFunc: (record: Record) => { + // return record.id % 2 === 0; + // } + // } + // ] + // }, + columns, + tooltip: { + isShowOverflowTextTooltip: true + }, + frozenColCount: 1, + bottomFrozenRowCount: 1, + rightFrozenColCount: 2, + overscrollBehavior: 'none', + autoWrapText: true, + heightMode: 'autoHeight', + dragHeaderMode: 'all', + keyboardOptions: { + pasteValueToCell: true + }, + eventOptions: { + preventDefaultContextMenu: false + }, + pagination: { + perPageCount: 100, + currentPage: 0 + } + // widthMode: 'adaptive' + }; + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + tableInstance.on('change_cell_value', arg => { + console.log(arg); + }); +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index f8dc8aa0b..3abf76bba 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -103,6 +103,10 @@ export const menus = [ { path: 'list-analysis', name: 'list-filter' + }, + { + path: 'list-analysis', + name: 'list-aggregation' } ] }, diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 8e2aea21f..30c3daed3 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -206,6 +206,16 @@ export class ListTable extends BaseTable implements ListTableAPI { if (table.internalProps.layoutMap.isHeader(col, row)) { const { title } = table.internalProps.layoutMap.getHeader(col, row); return typeof title === 'function' ? title() : title; + } else if (table.internalProps.layoutMap.isAggregation(col, row)) { + const column = table.internalProps.layoutMap.getBody(col, row); + if (table.internalProps.layoutMap.isTopAggregation(col, row) && column.aggregation?.showOnTop) { + return column.aggregator?.value(); + } else if ( + table.internalProps.layoutMap.isBottomAggregation(col, row) && + column.aggregation?.showOnTop === false + ) { + return column.aggregator?.value(); + } } const { field, fieldFormat } = table.internalProps.layoutMap.getBody(col, row); return table.getFieldData(fieldFormat || field, col, row); @@ -219,6 +229,9 @@ export class ListTable extends BaseTable implements ListTableAPI { if (table.internalProps.layoutMap.isHeader(col, row)) { const { title } = table.internalProps.layoutMap.getHeader(col, row); return typeof title === 'function' ? title() : title; + } else if (table.internalProps.layoutMap.isAggregation(col, row)) { + const column = table.internalProps.layoutMap.getBody(col, row); + return column.aggregator?.value(); } const { field } = table.internalProps.layoutMap.getBody(col, row); return table.getFieldData(field, col, row); @@ -427,11 +440,12 @@ export class ListTable extends BaseTable implements ListTableAPI { if (!layoutMap) { return; } - layoutMap.recordsCount = table.internalProps.dataSource?.length ?? 0; + layoutMap.recordsCount = + (table.internalProps.dataSource?.length ?? 0) + + (layoutMap.hasAggregation ? (layoutMap.hasAggregationOnTop && layoutMap.hasAggregationOnBottom ? 2 : 1) : 0); if (table.transpose) { table.rowCount = layoutMap.rowCount ?? 0; - table.colCount = - (table.internalProps.dataSource?.length ?? 0) * layoutMap.bodyRowSpanCount + layoutMap.headerLevelCount; + table.colCount = layoutMap.recordsCount * layoutMap.bodyRowSpanCount + layoutMap.headerLevelCount; table.frozenRowCount = 0; // table.frozenColCount = layoutMap.headerLevelCount; //这里不要这样写 这个setter会检查扁头宽度 可能将frozenColCount置为0 this.internalProps.frozenColCount = layoutMap.headerLevelCount ?? 0; @@ -443,8 +457,7 @@ export class ListTable extends BaseTable implements ListTableAPI { } } else { table.colCount = layoutMap.colCount ?? 0; - table.rowCount = - (table.internalProps.dataSource?.length ?? 0) * layoutMap.bodyRowSpanCount + layoutMap.headerLevelCount; + table.rowCount = layoutMap.recordsCount * layoutMap.bodyRowSpanCount + layoutMap.headerLevelCount; // table.frozenColCount = table.options.frozenColCount ?? 0; //这里不要这样写 这个setter会检查扁头宽度 可能将frozenColCount置为0 this.internalProps.frozenColCount = this.options.frozenColCount ?? 0; table.frozenRowCount = layoutMap.headerLevelCount; diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index a93b6e7fe..c5de6b30a 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -28,7 +28,7 @@ import type { BaseTableAPI, PivotTableProtected } from './ts-types/base-table'; import { Title } from './components/title/title'; import { cloneDeep } from '@visactor/vutils'; import { Env } from './tools/env'; -import type { LayouTreeNode } from './layout/pivot-layout-helper'; +import type { LayouTreeNode } from './layout/tree-helper'; import { TABLE_EVENT_TYPE } from './core/TABLE_EVENT_TYPE'; import { EditManeger } from './edit/edit-manager'; import * as editors from './edit/editors'; diff --git a/packages/vtable/src/core/tableHelper.ts b/packages/vtable/src/core/tableHelper.ts index 02c91e99c..86b141a63 100644 --- a/packages/vtable/src/core/tableHelper.ts +++ b/packages/vtable/src/core/tableHelper.ts @@ -59,6 +59,7 @@ export function _setRecords(table: ListTableAPI, records: any[] = []): void { data, table.internalProps.dataConfig, table.pagination, + table.internalProps.layoutMap.columnObjects, (table.options as any).hierarchyExpandLevel ?? (table._hasHierarchyTreeHeader?.() ? 1 : undefined) )); table.addReleaseObj(newDataSource); diff --git a/packages/vtable/src/data/CachedDataSource.ts b/packages/vtable/src/data/CachedDataSource.ts index eb5bf669f..0aad18a2c 100644 --- a/packages/vtable/src/data/CachedDataSource.ts +++ b/packages/vtable/src/data/CachedDataSource.ts @@ -8,6 +8,7 @@ import type { MaybePromiseOrUndefined } from '../ts-types'; import type { BaseTableAPI } from '../ts-types/base-table'; +import type { ColumnData } from '../ts-types/list-table/layout-map/api'; import type { DataSourceParam } from './DataSource'; import { DataSource } from './DataSource'; @@ -44,6 +45,7 @@ export class CachedDataSource extends DataSource { array: any[], dataConfig?: IListTableDataConfig, pagination?: IPagination, + columnObjs?: ColumnData[], hierarchyExpandLevel?: number ): CachedDataSource { return new CachedDataSource( @@ -59,6 +61,7 @@ export class CachedDataSource extends DataSource { }, dataConfig, pagination, + columnObjs, hierarchyExpandLevel ); } @@ -66,9 +69,10 @@ export class CachedDataSource extends DataSource { opt?: DataSourceParam, dataConfig?: IListTableDataConfig, pagination?: IPagination, + columnObjs?: ColumnData[], hierarchyExpandLevel?: number ) { - super(opt, dataConfig, pagination, hierarchyExpandLevel); + super(opt, dataConfig, pagination, columnObjs, hierarchyExpandLevel); this._recordCache = {}; this._fieldCache = {}; } diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index a0f4d7428..28c768d0d 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -1,5 +1,7 @@ import * as sort from '../tools/sort'; import type { + Aggregation, + AggregationRule, DataSourceAPI, FieldAssessor, FieldData, @@ -12,7 +14,7 @@ import type { MaybePromiseOrUndefined, SortOrder } from '../ts-types'; -import { HierarchyState } from '../ts-types'; +import { AggregationType, HierarchyState } from '../ts-types'; import { applyChainSafe, getOrApply, obj, isPromise, emptyFn } from '../tools/helper'; import { EventTarget } from '../event/EventTarget'; import { getValueByPath, isAllDigits } from '../tools/util'; @@ -20,6 +22,17 @@ import { calculateArrayDiff } from '../tools/diff-cell'; import { cloneDeep, isValid } from '@visactor/vutils'; import type { BaseTableAPI } from '../ts-types/base-table'; import { TABLE_EVENT_TYPE } from '../core/TABLE_EVENT_TYPE'; +import { + RecordAggregator, + type Aggregator, + SumAggregator, + CountAggregator, + MaxAggregator, + MinAggregator, + AvgAggregator, + NoneAggregator +} from '../dataset/statistics-helper'; +import type { ColumnData } from '../ts-types/list-table/layout-map/api'; /** * 判断字段数据是否为访问器的格式 @@ -158,17 +171,29 @@ export class DataSource extends EventTarget implements DataSourceAPI { } protected treeDataHierarchyState: Map = new Map(); beforeChangedRecordsMap: Record[] = []; + + // 注册聚合类型 + registedAggregators: { + [key: string]: { + new (dimension: string | string[], formatFun?: any, isRecord?: boolean): Aggregator; + }; + } = {}; + // columns对应各个字段的聚合类对象 + fieldAggregators: Aggregator[] = []; constructor( dataSourceObj?: DataSourceParam | DataSource, dataConfig?: IListTableDataConfig, pagination?: IPagination, + columnObjs?: ColumnData[], hierarchyExpandLevel?: number ) { super(); + this.registerAggregators(); this.dataSourceObj = dataSourceObj; this.dataConfig = dataConfig; this._get = dataSourceObj?.get.bind(dataSourceObj) || (undefined as any); - this._source = this.filterRecords(dataSourceObj?.source ?? dataSourceObj); + columnObjs && this._generateFieldAggragations(columnObjs); + this._source = this.processRecords(dataSourceObj?.source ?? dataSourceObj); this._sourceLength = this._source?.length || 0; this.sortedIndexMap = new Map(); @@ -209,20 +234,61 @@ export class DataSource extends EventTarget implements DataSourceAPI { } this.updatePagerData(); } - - filterRecords(records: any[]) { + //将聚合类型注册 收集到aggregators + registerAggregator(type: string, aggregator: any) { + this.registedAggregators[type] = aggregator; + } + //将聚合类型注册 + registerAggregators() { + this.registerAggregator(AggregationType.RECORD, RecordAggregator); + this.registerAggregator(AggregationType.SUM, SumAggregator); + this.registerAggregator(AggregationType.COUNT, CountAggregator); + this.registerAggregator(AggregationType.MAX, MaxAggregator); + this.registerAggregator(AggregationType.MIN, MinAggregator); + this.registerAggregator(AggregationType.AVG, AvgAggregator); + this.registerAggregator(AggregationType.NONE, NoneAggregator); + } + _generateFieldAggragations(columnObjs: ColumnData[]) { + for (let i = 0; i < columnObjs.length; i++) { + const field = columnObjs[i].field; + const aggragation = columnObjs[i].aggregation; + if (!aggragation || aggragation.aggregationType === AggregationType.NONE) { + continue; + } + const aggregator = new this.registedAggregators[aggragation.aggregationType](field as string); + this.fieldAggregators.push(aggregator); + columnObjs[i].aggregator = aggregator; + } + } + processRecords(records: any[]) { const filteredRecords = []; - if (this.dataConfig?.filterRules?.length >= 1) { + const isHasAggregation = this.fieldAggregators.length >= 1; + const isHasFilterRule = this.dataConfig?.filterRules?.length >= 1; + if (isHasFilterRule || isHasAggregation) { for (let i = 0, len = records.length; i < len; i++) { const record = records[i]; - if (this.filterRecord(record)) { - filteredRecords.push(record); + if (isHasFilterRule) { + if (this.filterRecord(record)) { + filteredRecords.push(record); + isHasAggregation && this.processRecord(record); + } + } else if (isHasAggregation) { + this.processRecord(record); } } - return filteredRecords; + if (isHasFilterRule) { + return filteredRecords; + } } return records; } + + processRecord(record: any) { + for (let i = 0; i < this.fieldAggregators.length; i++) { + const aggregator = this.fieldAggregators[i]; + aggregator.push(record); + } + } /** * 初始化子节点的层次信息 * @param indexKey 父节点的indexKey 即currentLevel-1的节点 @@ -786,7 +852,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { updateFilterRulesForSorted(filterRules?: FilterRules): void { this.dataConfig.filterRules = filterRules; - this._source = this.filterRecords(this.dataSourceObj?.source ?? this.dataSourceObj); + this._source = this.processRecords(this.dataSourceObj?.source ?? this.dataSourceObj); this._sourceLength = this._source?.length || 0; this.sortedIndexMap.clear(); this.currentIndexedData = Array.from({ length: this._sourceLength }, (_, i) => i); @@ -798,7 +864,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { updateFilterRules(filterRules?: FilterRules): void { this.dataConfig.filterRules = filterRules; - this._source = this.filterRecords(this.dataSourceObj?.source ?? this.dataSourceObj); + this._source = this.processRecords(this.dataSourceObj?.source ?? this.dataSourceObj); this._sourceLength = this._source?.length || 0; // 初始化currentIndexedData 正常未排序。设置其状态 this.currentIndexedData = Array.from({ length: this._sourceLength }, (_, i) => i); diff --git a/packages/vtable/src/layout/layout-helper.ts b/packages/vtable/src/layout/layout-helper.ts new file mode 100644 index 000000000..b33d14079 --- /dev/null +++ b/packages/vtable/src/layout/layout-helper.ts @@ -0,0 +1,35 @@ +import type { ColumnData } from '../ts-types/list-table/layout-map/api'; +import type { SimpleHeaderLayoutMap } from './simple-header-layout'; + +export function checkHasAggregation(layoutMap: SimpleHeaderLayoutMap) { + const columnObjects = layoutMap.columnObjects; + for (let i = 0; i < columnObjects.length; i++) { + const column = columnObjects[i]; + if ((column as ColumnData)?.aggregation) { + return true; + } + } + return false; +} + +export function checkHasAggregationOnTop(layoutMap: SimpleHeaderLayoutMap) { + const columnObjects = layoutMap.columnObjects; + for (let i = 0; i < columnObjects.length; i++) { + const column = columnObjects[i]; + if ((column as ColumnData)?.aggregation && (column as ColumnData)?.aggregation.showOnTop) { + return true; + } + } + return false; +} + +export function checkHasAggregationOnBottom(layoutMap: SimpleHeaderLayoutMap) { + const columnObjects = layoutMap.columnObjects; + for (let i = 0; i < columnObjects.length; i++) { + const column = columnObjects[i]; + if ((column as ColumnData)?.aggregation && (column as ColumnData)?.aggregation.showOnTop === false) { + return true; + } + } + return false; +} diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index ac5d1d387..28a0b4967 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -45,14 +45,8 @@ import { isCartesianChart, isHasCartesianChartInline } from './chart-helper/get-chart-spec'; -import type { LayouTreeNode, IPivotLayoutHeadNode } from './pivot-layout-helper'; -import { - DimensionTree, - countLayoutTree, - dealHeader, - dealHeaderForTreeMode, - generateLayoutTree -} from './pivot-layout-helper'; +import type { LayouTreeNode, IPivotLayoutHeadNode } from './tree-helper'; +import { DimensionTree, countLayoutTree, dealHeader, dealHeaderForTreeMode, generateLayoutTree } from './tree-helper'; import type { Dataset } from '../dataset/dataset'; import { cloneDeep, isArray, isValid } from '@visactor/vutils'; import type { TextStyle } from '../body-helper/style'; diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 3746c269c..06796396e 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -12,6 +12,7 @@ import type { WidthData } from '../ts-types/list-table/layout-map/api'; import { checkHasChart, getChartDataId } from './chart-helper/get-chart-spec'; +import { checkHasAggregation, checkHasAggregationOnBottom, checkHasAggregationOnTop } from './layout-helper'; // import { EmptyDataCache } from './utils'; // let seqId = 0; @@ -31,6 +32,9 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { _showHeader = true; _recordsCount = 0; _table: ListTable; + _hasAggregation: boolean = false; + _hasAggregationOnTop: boolean = false; + _hasAggregationOnBottom: boolean = false; // 缓存行号列号对应的cellRange 需要注意当表头位置拖拽后 这个缓存的行列号已不准确 进行重置 private _cellRangeMap: Map; //存储单元格的行列号范围 针对解决是否为合并单元格情况 constructor(table: ListTable, columns: ColumnsDefine, showHeader: boolean, hierarchyIndent: number) { @@ -45,6 +49,9 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { o[e.id as number] = e; return o; }, {} as { [key in LayoutObjectId]: HeaderData }); + this._hasAggregation = checkHasAggregation(this); + this._hasAggregationOnBottom = checkHasAggregationOnBottom(this); + this._hasAggregationOnTop = checkHasAggregationOnTop(this); // this._headerObjectFieldKey = this._headerObjects.reduce((o, e) => { // o[e.fieldKey] = e; // return o; @@ -74,6 +81,70 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { } return false; } + isAggregation(col: number, row: number): boolean { + // const column = this.getBody(col, row); + // const aggregation = column.aggregation; + if (this.hasAggregation) { + if (this.hasAggregationOnBottom) { + if (this.transpose) { + if (col === this.colCount - 1) { + return true; + } + } + if (row === this.rowCount - 1) { + return true; + } + } + if (this.hasAggregationOnTop) { + if (this.transpose) { + if (col === this.rowHeaderLevelCount) { + return true; + } + } + if (row === this.columnHeaderLevelCount) { + return true; + } + } + } + return false; + } + isTopAggregation(col: number, row: number): boolean { + if (this.hasAggregationOnTop) { + if (this.transpose) { + if (col === this.rowHeaderLevelCount) { + return true; + } + } + if (row === this.columnHeaderLevelCount) { + return true; + } + } + return false; + } + isBottomAggregation(col: number, row: number): boolean { + if (this.hasAggregationOnBottom) { + if (this.transpose) { + if (col === this.colCount - 1) { + return true; + } + } + if (row === this.rowCount - 1) { + return true; + } + } + return false; + } + get hasAggregation() { + return this._hasAggregation; + } + + get hasAggregationOnTop() { + return this._hasAggregationOnTop; + } + + get hasAggregationOnBottom() { + return this._hasAggregationOnBottom; + } getCellLocation(col: number, row: number): CellLocation { if (this.isHeader(col, row)) { if (this.transpose) { @@ -620,19 +691,22 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { ); } getRecordIndexByCell(col: number, row: number): number { + const skipRowCount = this.hasAggregationOnTop ? this.headerLevelCount + 1 : this.headerLevelCount; if (this.transpose) { - if (col < this.headerLevelCount) { + if (col < skipRowCount) { return -1; } - return col - this.headerLevelCount; + return col - skipRowCount; } - if (row < this.headerLevelCount) { + + if (row < skipRowCount) { return -1; } - return row - this.headerLevelCount; + return row - skipRowCount; } getRecordStartRowByRecordIndex(index: number): number { - return this.headerLevelCount + index; + const skipRowCount = this.hasAggregationOnTop ? this.headerLevelCount + 1 : this.headerLevelCount; + return skipRowCount + index; } private _addHeaders( row: number, @@ -651,7 +725,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { // captionIcon, headerIcon: hd.headerIcon, field: (hd as ColumnDefine).field, - fieldFormat: (hd as ColumnDefine).fieldFormat, + // fieldFormat: (hd as ColumnDefine).fieldFormat, style: hd.headerStyle, headerType: hd.headerType ?? 'text', dropDownMenu: hd.dropDownMenu, @@ -676,7 +750,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { this._columns.push({ id: this.seqId++, field: colDef.field, - fieldKey: colDef.fieldKey, + // fieldKey: colDef.fieldKey, fieldFormat: colDef.fieldFormat, width: colDef.width, minWidth: colDef.minWidth, @@ -689,7 +763,8 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { style: colDef.style, define: colDef, columnWidthComputeMode: colDef.columnWidthComputeMode, - disableColumnResize: colDef?.disableColumnResize + disableColumnResize: colDef?.disableColumnResize, + aggregation: colDef.aggregation ? Object.assign({ showOnTop: false }, colDef.aggregation) : undefined }); for (let r = row + 1; r < this._headerCellIds.length; r++) { this._headerCellIds[r][col] = id; diff --git a/packages/vtable/src/layout/pivot-layout-helper.ts b/packages/vtable/src/layout/tree-helper.ts similarity index 100% rename from packages/vtable/src/layout/pivot-layout-helper.ts rename to packages/vtable/src/layout/tree-helper.ts diff --git a/packages/vtable/src/ts-types/list-table/define/basic-define.ts b/packages/vtable/src/ts-types/list-table/define/basic-define.ts index 45fe793fd..0c0618ec7 100644 --- a/packages/vtable/src/ts-types/list-table/define/basic-define.ts +++ b/packages/vtable/src/ts-types/list-table/define/basic-define.ts @@ -6,15 +6,12 @@ import type { ColumnIconOption } from '../../icon'; import type { MenuListItem } from '../../menu'; import type { BaseTableAPI } from '../../base-table'; import type { IEditor } from '@visactor/vtable-editors'; +import type { Aggregation, AggregationType } from '../../new-data-set'; // eslint-disable-next-line no-unused-vars export interface IBasicHeaderDefine { // 表头的标题 title?: string | (() => string); //支持图文混合 - /** @deprecated - * 已废除该配置 标题中显示图标 现在请使用headerIcon进行配置 - */ - // captionIcon?: ColumnIconOption; /** 表头Icon配置 */ headerIcon?: string | ColumnIconOption | (string | ColumnIconOption)[]; // | ((args: CellInfo) => string | ColumnIconOption | (string | ColumnIconOption)[]); @@ -86,4 +83,5 @@ export interface IBasicColumnBodyDefine { customRender?: ICustomRender; customLayout?: ICustomLayout; editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); + aggregation?: Aggregation; } diff --git a/packages/vtable/src/ts-types/list-table/layout-map/api.ts b/packages/vtable/src/ts-types/list-table/layout-map/api.ts index 8143e03bb..4ab921462 100644 --- a/packages/vtable/src/ts-types/list-table/layout-map/api.ts +++ b/packages/vtable/src/ts-types/list-table/layout-map/api.ts @@ -18,8 +18,10 @@ import type { FieldKeyDef, CustomRenderFunctionArg, SparklineSpec, - HierarchyState + HierarchyState, + Aggregation } from '../../'; +import type { Aggregator } from '../../../dataset/statistics-helper'; import type { HeaderDefine, ColumnDefine, ColumnBodyDefine } from '../define'; @@ -103,7 +105,7 @@ export interface WidthData { export interface ColumnData extends WidthData { id: LayoutObjectId; field: FieldDef; - fieldKey?: FieldKeyDef; + // fieldKey?: FieldKeyDef; fieldFormat?: FieldFormat; // icon?: ColumnIconOption | ColumnIconOption[]; icon?: @@ -129,6 +131,8 @@ export interface ColumnData extends WidthData { * 是否禁用调整列宽,如果是转置表格或者是透视表的指标是行方向指定 那该配置不生效 */ disableColumnResize?: boolean; + aggregation?: Aggregation; + aggregator?: Aggregator; } export interface IndicatorData extends WidthData { diff --git a/packages/vtable/src/ts-types/new-data-set.ts b/packages/vtable/src/ts-types/new-data-set.ts index 3bfda55b5..efc73dc50 100644 --- a/packages/vtable/src/ts-types/new-data-set.ts +++ b/packages/vtable/src/ts-types/new-data-set.ts @@ -228,3 +228,10 @@ export type CollectValueBy = { sortBy?: string[]; }; export type CollectedValue = { max?: number; min?: number } | Array; + +//#region 提供给基本表格的类型 +export type Aggregation = { + aggregationType: AggregationType; + showOnTop?: boolean; +}; +//#endregion From 903856da314ba6c9cb928a641c04103d08a6067a Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Sun, 4 Feb 2024 19:47:16 +0800 Subject: [PATCH 08/63] docs: update changlog of rush --- ...8-feature-listtable-caculate_2024-02-04-11-47.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-04-11-47.json diff --git a/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-04-11-47.json b/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-04-11-47.json new file mode 100644 index 000000000..da4cedf8c --- /dev/null +++ b/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-04-11-47.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: add aggregation for list table column\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 2435b2714120479872b7e8278a847594c01930b9 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 5 Feb 2024 20:31:20 +0800 Subject: [PATCH 09/63] feat: aggregator is array --- .../list-analysis/list-aggregation.ts | 18 ++- packages/vtable/src/ListTable.ts | 27 ++-- packages/vtable/src/data/DataSource.ts | 20 ++- packages/vtable/src/layout/layout-helper.ts | 27 +++- .../vtable/src/layout/simple-header-layout.ts | 116 ++++++++++++++---- .../list-table/define/basic-define.ts | 2 +- .../src/ts-types/list-table/layout-map/api.ts | 4 +- 7 files changed, 161 insertions(+), 53 deletions(-) diff --git a/packages/vtable/examples/list-analysis/list-aggregation.ts b/packages/vtable/examples/list-analysis/list-aggregation.ts index 45620d17f..fd3906b25 100644 --- a/packages/vtable/examples/list-analysis/list-aggregation.ts +++ b/packages/vtable/examples/list-analysis/list-aggregation.ts @@ -97,10 +97,18 @@ export function createTable() { field: 'salary', title: 'salary', width: 100, - aggregation: { - aggregationType: AggregationType.MAX, - showOnTop: true - } + aggregation: [ + { + aggregationType: AggregationType.MAX + }, + { + aggregationType: AggregationType.MIN + }, + { + aggregationType: AggregationType.AVG, + showOnTop: true + } + ] } ]; const option: VTable.ListTableConstructorOptions = { @@ -124,7 +132,7 @@ export function createTable() { isShowOverflowTextTooltip: true }, frozenColCount: 1, - bottomFrozenRowCount: 1, + bottomFrozenRowCount: 2, rightFrozenColCount: 2, overscrollBehavior: 'none', autoWrapText: true, diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 30c3daed3..8dfb34662 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -207,14 +207,12 @@ export class ListTable extends BaseTable implements ListTableAPI { const { title } = table.internalProps.layoutMap.getHeader(col, row); return typeof title === 'function' ? title() : title; } else if (table.internalProps.layoutMap.isAggregation(col, row)) { - const column = table.internalProps.layoutMap.getBody(col, row); - if (table.internalProps.layoutMap.isTopAggregation(col, row) && column.aggregation?.showOnTop) { - return column.aggregator?.value(); - } else if ( - table.internalProps.layoutMap.isBottomAggregation(col, row) && - column.aggregation?.showOnTop === false - ) { - return column.aggregator?.value(); + if (table.internalProps.layoutMap.isTopAggregation(col, row)) { + const aggregator = table.internalProps.layoutMap.getAggregatorOnTop(col, row); + return aggregator?.value(); + } else if (table.internalProps.layoutMap.isBottomAggregation(col, row)) { + const aggregator = table.internalProps.layoutMap.getAggregatorOnBottom(col, row); + return aggregator?.value(); } } const { field, fieldFormat } = table.internalProps.layoutMap.getBody(col, row); @@ -230,8 +228,13 @@ export class ListTable extends BaseTable implements ListTableAPI { const { title } = table.internalProps.layoutMap.getHeader(col, row); return typeof title === 'function' ? title() : title; } else if (table.internalProps.layoutMap.isAggregation(col, row)) { - const column = table.internalProps.layoutMap.getBody(col, row); - return column.aggregator?.value(); + if (table.internalProps.layoutMap.isTopAggregation(col, row)) { + const aggregator = table.internalProps.layoutMap.getAggregatorOnTop(col, row); + return aggregator?.value(); + } else if (table.internalProps.layoutMap.isBottomAggregation(col, row)) { + const aggregator = table.internalProps.layoutMap.getAggregatorOnBottom(col, row); + return aggregator?.value(); + } } const { field } = table.internalProps.layoutMap.getBody(col, row); return table.getFieldData(field, col, row); @@ -442,7 +445,9 @@ export class ListTable extends BaseTable implements ListTableAPI { } layoutMap.recordsCount = (table.internalProps.dataSource?.length ?? 0) + - (layoutMap.hasAggregation ? (layoutMap.hasAggregationOnTop && layoutMap.hasAggregationOnBottom ? 2 : 1) : 0); + layoutMap.hasAggregationOnTopCount + + layoutMap.hasAggregationOnBottomCount; + if (table.transpose) { table.rowCount = layoutMap.rowCount ?? 0; table.colCount = layoutMap.recordsCount * layoutMap.bodyRowSpanCount + layoutMap.headerLevelCount; diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index 28c768d0d..2bb0a9a8d 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -252,12 +252,24 @@ export class DataSource extends EventTarget implements DataSourceAPI { for (let i = 0; i < columnObjs.length; i++) { const field = columnObjs[i].field; const aggragation = columnObjs[i].aggregation; - if (!aggragation || aggragation.aggregationType === AggregationType.NONE) { + if (!aggragation) { continue; } - const aggregator = new this.registedAggregators[aggragation.aggregationType](field as string); - this.fieldAggregators.push(aggregator); - columnObjs[i].aggregator = aggregator; + if (Array.isArray(aggragation)) { + for (let j = 0; j < aggragation.length; j++) { + const item = aggragation[j]; + const aggregator = new this.registedAggregators[item.aggregationType](field as string); + this.fieldAggregators.push(aggregator); + if (!columnObjs[i].aggregator) { + columnObjs[i].aggregator = []; + } + columnObjs[i].aggregator.push(aggregator); + } + } else { + const aggregator = new this.registedAggregators[aggragation.aggregationType](field as string); + this.fieldAggregators.push(aggregator); + columnObjs[i].aggregator = aggregator; + } } } processRecords(records: any[]) { diff --git a/packages/vtable/src/layout/layout-helper.ts b/packages/vtable/src/layout/layout-helper.ts index b33d14079..1cfa3cb18 100644 --- a/packages/vtable/src/layout/layout-helper.ts +++ b/packages/vtable/src/layout/layout-helper.ts @@ -1,3 +1,4 @@ +import type { Aggregation } from '../ts-types'; import type { ColumnData } from '../ts-types/list-table/layout-map/api'; import type { SimpleHeaderLayoutMap } from './simple-header-layout'; @@ -14,22 +15,36 @@ export function checkHasAggregation(layoutMap: SimpleHeaderLayoutMap) { export function checkHasAggregationOnTop(layoutMap: SimpleHeaderLayoutMap) { const columnObjects = layoutMap.columnObjects; + let count = 0; for (let i = 0; i < columnObjects.length; i++) { const column = columnObjects[i]; - if ((column as ColumnData)?.aggregation && (column as ColumnData)?.aggregation.showOnTop) { - return true; + if ((column as ColumnData)?.aggregation) { + if (Array.isArray((column as ColumnData)?.aggregation)) { + count = ((column as ColumnData).aggregation as Array).filter( + item => item.showOnTop === true + ).length; + } else if (((column as ColumnData).aggregation as Aggregation).showOnTop === true) { + count = 1; + } } } - return false; + return count; } export function checkHasAggregationOnBottom(layoutMap: SimpleHeaderLayoutMap) { const columnObjects = layoutMap.columnObjects; + let count = 0; for (let i = 0; i < columnObjects.length; i++) { const column = columnObjects[i]; - if ((column as ColumnData)?.aggregation && (column as ColumnData)?.aggregation.showOnTop === false) { - return true; + if ((column as ColumnData)?.aggregation) { + if (Array.isArray((column as ColumnData)?.aggregation)) { + count = ((column as ColumnData).aggregation as Array).filter( + item => item.showOnTop === false + ).length; + } else if (((column as ColumnData).aggregation as Aggregation).showOnTop === false) { + count = 1; + } } } - return false; + return count; } diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 06796396e..323df91e7 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -2,7 +2,15 @@ import { isValid } from '@visactor/vutils'; import type { ListTable } from '../ListTable'; import { DefaultSparklineSpec } from '../tools/global'; -import type { CellAddress, CellRange, CellLocation, IListTableCellHeaderPaths, LayoutObjectId } from '../ts-types'; +import type { + CellAddress, + CellRange, + CellLocation, + IListTableCellHeaderPaths, + LayoutObjectId, + AggregationType, + Aggregation +} from '../ts-types'; import type { ColumnsDefine, TextColumnDefine } from '../ts-types/list-table/define'; import type { ColumnData, @@ -13,6 +21,7 @@ import type { } from '../ts-types/list-table/layout-map/api'; import { checkHasChart, getChartDataId } from './chart-helper/get-chart-spec'; import { checkHasAggregation, checkHasAggregationOnBottom, checkHasAggregationOnTop } from './layout-helper'; +import type { Aggregator } from '../dataset/statistics-helper'; // import { EmptyDataCache } from './utils'; // let seqId = 0; @@ -33,8 +42,8 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { _recordsCount = 0; _table: ListTable; _hasAggregation: boolean = false; - _hasAggregationOnTop: boolean = false; - _hasAggregationOnBottom: boolean = false; + _hasAggregationOnTopCount: number = 0; + _hasAggregationOnBottomCount: number = 0; // 缓存行号列号对应的cellRange 需要注意当表头位置拖拽后 这个缓存的行列号已不准确 进行重置 private _cellRangeMap: Map; //存储单元格的行列号范围 针对解决是否为合并单元格情况 constructor(table: ListTable, columns: ColumnsDefine, showHeader: boolean, hierarchyIndent: number) { @@ -50,8 +59,8 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { return o; }, {} as { [key in LayoutObjectId]: HeaderData }); this._hasAggregation = checkHasAggregation(this); - this._hasAggregationOnBottom = checkHasAggregationOnBottom(this); - this._hasAggregationOnTop = checkHasAggregationOnTop(this); + this._hasAggregationOnBottomCount = checkHasAggregationOnBottom(this); + this._hasAggregationOnTopCount = checkHasAggregationOnTop(this); // this._headerObjectFieldKey = this._headerObjects.reduce((o, e) => { // o[e.fieldKey] = e; // return o; @@ -85,23 +94,23 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { // const column = this.getBody(col, row); // const aggregation = column.aggregation; if (this.hasAggregation) { - if (this.hasAggregationOnBottom) { + if (this.hasAggregationOnBottomCount) { if (this.transpose) { - if (col === this.colCount - 1) { + if (col === this.colCount - this.hasAggregationOnBottomCount) { return true; } } - if (row === this.rowCount - 1) { + if (row >= this.rowCount - this.hasAggregationOnBottomCount) { return true; } } - if (this.hasAggregationOnTop) { + if (this.hasAggregationOnTopCount) { if (this.transpose) { - if (col === this.rowHeaderLevelCount) { + if (col >= this.rowHeaderLevelCount && col < this.rowHeaderLevelCount + this.hasAggregationOnTopCount) { return true; } } - if (row === this.columnHeaderLevelCount) { + if (row >= this.columnHeaderLevelCount && row < this.columnHeaderLevelCount + this.hasAggregationOnTopCount) { return true; } } @@ -109,26 +118,26 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { return false; } isTopAggregation(col: number, row: number): boolean { - if (this.hasAggregationOnTop) { + if (this.hasAggregationOnTopCount) { if (this.transpose) { - if (col === this.rowHeaderLevelCount) { + if (col >= this.rowHeaderLevelCount && col < this.rowHeaderLevelCount + this.hasAggregationOnTopCount) { return true; } } - if (row === this.columnHeaderLevelCount) { + if (row >= this.columnHeaderLevelCount && row < this.columnHeaderLevelCount + this.hasAggregationOnTopCount) { return true; } } return false; } isBottomAggregation(col: number, row: number): boolean { - if (this.hasAggregationOnBottom) { + if (this.hasAggregationOnBottomCount) { if (this.transpose) { - if (col === this.colCount - 1) { + if (col === this.colCount - this.hasAggregationOnBottomCount) { return true; } } - if (row === this.rowCount - 1) { + if (row >= this.rowCount - this.hasAggregationOnBottomCount) { return true; } } @@ -138,12 +147,62 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { return this._hasAggregation; } - get hasAggregationOnTop() { - return this._hasAggregationOnTop; + get hasAggregationOnTopCount() { + return this._hasAggregationOnTopCount; + } + + get hasAggregationOnBottomCount() { + return this._hasAggregationOnBottomCount; } - get hasAggregationOnBottom() { - return this._hasAggregationOnBottom; + getAggregatorOnTop(col: number, row: number) { + const column = this.getBody(col, row); + const aggregators = column.aggregator; + const aggregation = column.aggregation; + if (Array.isArray(aggregation)) { + const topAggregationIndexs = aggregation.reduce((indexs, agg, index) => { + if (agg.showOnTop) { + indexs.push(index); + } + return indexs; + }, []); + const topAggregators = topAggregationIndexs.map(index => aggregators[index]); + if (this.transpose) { + return (topAggregators as Aggregator[])[col - this.rowHeaderLevelCount]; + } + return (topAggregators as Aggregator[])[row - this.columnHeaderLevelCount]; + } + if (this.transpose && col - this.rowHeaderLevelCount === 0) { + return (aggregation as Aggregation)?.showOnTop ? (aggregators as Aggregator) : null; + } else if (!this.transpose && row - this.columnHeaderLevelCount === 0) { + return (aggregation as Aggregation)?.showOnTop ? (aggregators as Aggregator) : null; + } + return null; + } + + getAggregatorOnBottom(col: number, row: number) { + const column = this.getBody(col, row); + const aggregators = column.aggregator; + const aggregation = column.aggregation; + if (Array.isArray(aggregation)) { + const bottomAggregationIndexs = aggregation.reduce((indexs, agg, index) => { + if (agg.showOnTop === false) { + indexs.push(index); + } + return indexs; + }, []); + const bottomAggregators = bottomAggregationIndexs.map(index => aggregators[index]); + if (this.transpose) { + return (bottomAggregators as Aggregator[])[col - (this.colCount - this.hasAggregationOnBottomCount)]; + } + return (bottomAggregators as Aggregator[])[row - (this.rowCount - this.hasAggregationOnBottomCount)]; + } + if (this.transpose && col - (this.colCount - this.hasAggregationOnBottomCount) === 0) { + return (aggregation as Aggregation)?.showOnTop === false ? (aggregators as Aggregator) : null; + } else if (!this.transpose && row - (this.rowCount - this.hasAggregationOnBottomCount) === 0) { + return (aggregation as Aggregation)?.showOnTop === false ? (aggregators as Aggregator) : null; + } + return null; } getCellLocation(col: number, row: number): CellLocation { if (this.isHeader(col, row)) { @@ -691,7 +750,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { ); } getRecordIndexByCell(col: number, row: number): number { - const skipRowCount = this.hasAggregationOnTop ? this.headerLevelCount + 1 : this.headerLevelCount; + const skipRowCount = this.hasAggregationOnTopCount ? this.headerLevelCount + 1 : this.headerLevelCount; if (this.transpose) { if (col < skipRowCount) { return -1; @@ -705,7 +764,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { return row - skipRowCount; } getRecordStartRowByRecordIndex(index: number): number { - const skipRowCount = this.hasAggregationOnTop ? this.headerLevelCount + 1 : this.headerLevelCount; + const skipRowCount = this.hasAggregationOnTopCount ? this.headerLevelCount + 1 : this.headerLevelCount; return skipRowCount + index; } private _addHeaders( @@ -764,7 +823,16 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { define: colDef, columnWidthComputeMode: colDef.columnWidthComputeMode, disableColumnResize: colDef?.disableColumnResize, - aggregation: colDef.aggregation ? Object.assign({ showOnTop: false }, colDef.aggregation) : undefined + aggregation: colDef.aggregation + ? Array.isArray(colDef.aggregation) + ? colDef.aggregation.map(item => { + if (!isValid(item.showOnTop)) { + item.showOnTop = false; + } + return item; + }) + : Object.assign({ showOnTop: false }, colDef.aggregation) + : undefined }); for (let r = row + 1; r < this._headerCellIds.length; r++) { this._headerCellIds[r][col] = id; diff --git a/packages/vtable/src/ts-types/list-table/define/basic-define.ts b/packages/vtable/src/ts-types/list-table/define/basic-define.ts index 0c0618ec7..653f1b33a 100644 --- a/packages/vtable/src/ts-types/list-table/define/basic-define.ts +++ b/packages/vtable/src/ts-types/list-table/define/basic-define.ts @@ -83,5 +83,5 @@ export interface IBasicColumnBodyDefine { customRender?: ICustomRender; customLayout?: ICustomLayout; editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); - aggregation?: Aggregation; + aggregation?: Aggregation | Aggregation[]; } diff --git a/packages/vtable/src/ts-types/list-table/layout-map/api.ts b/packages/vtable/src/ts-types/list-table/layout-map/api.ts index 4ab921462..a42c04911 100644 --- a/packages/vtable/src/ts-types/list-table/layout-map/api.ts +++ b/packages/vtable/src/ts-types/list-table/layout-map/api.ts @@ -131,8 +131,8 @@ export interface ColumnData extends WidthData { * 是否禁用调整列宽,如果是转置表格或者是透视表的指标是行方向指定 那该配置不生效 */ disableColumnResize?: boolean; - aggregation?: Aggregation; - aggregator?: Aggregator; + aggregation?: Aggregation | Aggregation[]; + aggregator?: Aggregator | Aggregator[]; } export interface IndicatorData extends WidthData { From 663b69c27c23f9a9cc8b374f57c0d19e5089bf37 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 6 Feb 2024 16:59:24 +0800 Subject: [PATCH 10/63] feat: list table aggregation support transpose and update --- packages/vtable/examples/header/merge-cell.ts | 201 ++++++++++++++++-- .../list-analysis/list-aggregation.ts | 92 +++++++- .../pivot-analysis-aggregationRules.ts | 4 +- packages/vtable/src/ListTable.ts | 14 +- packages/vtable/src/data/DataSource.ts | 17 +- packages/vtable/src/layout/layout-helper.ts | 18 +- .../vtable/src/layout/simple-header-layout.ts | 32 +-- packages/vtable/src/ts-types/base-table.ts | 2 + packages/vtable/src/ts-types/new-data-set.ts | 7 +- 9 files changed, 327 insertions(+), 60 deletions(-) diff --git a/packages/vtable/examples/header/merge-cell.ts b/packages/vtable/examples/header/merge-cell.ts index c67574f88..08e701e39 100644 --- a/packages/vtable/examples/header/merge-cell.ts +++ b/packages/vtable/examples/header/merge-cell.ts @@ -24,6 +24,121 @@ export function createTable() { id: 4, name: 'd' }, + { + progress: 28, + id: 5, + name: 'e' + }, + { + progress: 100, + id: 1, + name: 'a' + }, + { + progress: 80, + id: 2, + name: 'a' + }, + { + progress: 1, + id: 3, + name: 'c' + }, + { + progress: 55, + id: 4, + name: 'd' + }, + { + progress: 28, + id: 5, + name: 'e' + }, + { + progress: 100, + id: 1, + name: 'a' + }, + { + progress: 80, + id: 2, + name: 'a' + }, + { + progress: 1, + id: 3, + name: 'c' + }, + { + progress: 55, + id: 4, + name: 'd' + }, + { + progress: 28, + id: 5, + name: 'e' + }, + { + progress: 100, + id: 1, + name: 'a' + }, + { + progress: 80, + id: 2, + name: 'a' + }, + { + progress: 1, + id: 3, + name: 'c' + }, + { + progress: 55, + id: 4, + name: 'd' + }, + { + progress: 28, + id: 5, + name: 'e' + }, + { + progress: 100, + id: 1, + name: 'a' + }, + { + progress: 80, + id: 2, + name: 'a' + }, + { + progress: 1, + id: 3, + name: 'c' + }, + { + progress: 55, + id: 4, + name: 'd' + }, + { + progress: 28, + id: 5, + name: 'e' + }, + { + progress: 1, + id: 3, + name: 'c' + }, + { + progress: 55, + id: 4, + name: 'd' + }, { progress: 28, id: 5, @@ -40,7 +155,10 @@ export function createTable() { }, title: 'progress', description: '这是一个标题的详细描述', - width: 150 + width: 150, + style: { + // textStick: true + } }, { field: 'id', @@ -57,6 +175,9 @@ export function createTable() { } return v1 === v2 ? 0 : v1 > v2 ? 1 : -1; }, + style: { + textStick: true + }, width: 100, mergeCell: true }, @@ -112,31 +233,79 @@ export function createTable() { console.log(v, v2); return v === v2; } + }, + { + field: 'id', + title: 'ID说明', + width: 150 + }, + { + field: 'id', + title: 'ID说明', + width: 150 + }, + { + field: 'id', + title: 'ID说明', + width: 150 + }, + { + field: 'id', + title: 'ID说明', + width: 150 + }, + { + field: 'id', + title: 'ID说明', + width: 150 + }, + { + field: 'id', + title: 'ID说明', + width: 150 + }, + { + field: 'id', + title: 'ID说明', + width: 150 + }, + { + field: 'id', + title: 'ID说明', + width: 150 } ], showFrozenIcon: true, //显示VTable内置冻结列图标 widthMode: 'standard', - allowFrozenColCount: 2 + allowFrozenColCount: 2, + customMergeCell: (col, row, table) => { + if (col <= 1 && row > 0 && row < 11) { + return { + text: 'customMergeCell', + range: { + start: { + col: 0, + row: 1 + }, + end: { + col: 1, + row: 10 + } + }, + style: { + bgColor: 'red', + textStick: true + } + }; + } + } }; const instance = new ListTable(option); - + window.tableInstance = instance; //设置表格数据 instance.setRecords(personsDataSource, { field: 'id', order: 'desc' }); - // instance.setRecords(personsDataSource); - - VTable.bindDebugTool(instance.scenegraph.stage as any, { - customGrapicKeys: ['role', '_updateTag'] - }); - - // instance.updateSortState({ - // field: 'id', - // order: 'desc', - // }); - - // 只为了方便控制太调试用,不要拷贝 - window.tableInstance = instance; } diff --git a/packages/vtable/examples/list-analysis/list-aggregation.ts b/packages/vtable/examples/list-analysis/list-aggregation.ts index fd3906b25..b6907505b 100644 --- a/packages/vtable/examples/list-analysis/list-aggregation.ts +++ b/packages/vtable/examples/list-analysis/list-aggregation.ts @@ -17,7 +17,7 @@ const generatePersons = count => { }; export function createTable() { - const records = generatePersons(30); + const records = generatePersons(300); const columns: VTable.ColumnsDefine = [ { field: '', @@ -89,9 +89,15 @@ export function createTable() { field: 'salary', title: 'salary', width: 100, - aggregation: { - aggregationType: AggregationType.AVG - } + aggregation: [ + { + aggregationType: AggregationType.AVG + }, + { + aggregationType: AggregationType.SUM, + showOnTop: true + } + ] }, { field: 'salary', @@ -99,14 +105,23 @@ export function createTable() { width: 100, aggregation: [ { - aggregationType: AggregationType.MAX + aggregationType: AggregationType.MAX, + formatFun(value) { + return '最高薪资:' + Math.round(value) + '元'; + } }, { - aggregationType: AggregationType.MIN + aggregationType: AggregationType.MIN, + formatFun(value) { + return '最低薪资:' + Math.round(value) + '元'; + } }, { aggregationType: AggregationType.AVG, - showOnTop: true + showOnTop: false, + formatFun(value, col, row, table) { + return '平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } } ] } @@ -131,11 +146,12 @@ export function createTable() { tooltip: { isShowOverflowTextTooltip: true }, - frozenColCount: 1, - bottomFrozenRowCount: 2, - rightFrozenColCount: 2, + // frozenColCount: 1, + bottomFrozenRowCount: 3, + rightFrozenColCount: 1, overscrollBehavior: 'none', autoWrapText: true, + // widthMode: 'autoWidth', heightMode: 'autoHeight', dragHeaderMode: 'all', keyboardOptions: { @@ -147,10 +163,66 @@ export function createTable() { pagination: { perPageCount: 100, currentPage: 0 + }, + theme: VTable.themes.DEFAULT.extends({ + cornerRightBottomCellStyle: { + bgColor: 'rgba(1,1,1,0.1)', + borderColor: 'red' + } + }), + customMergeCell: (col, row, table) => { + if (col >= 0 && col < table.colCount && row === table.rowCount - 1) { + return { + text: '统计数据中平均薪资:' + table.getCellOriginValue(table.colCount - 1, table.rowCount - 1), + range: { + start: { + col: 0, + row: table.rowCount - 1 + }, + end: { + col: table.colCount - 1, + row: table.rowCount - 1 + } + }, + style: { + borderLineWidth: [6, 1, 1, 1], + borderColor: ['gray'], + textAlign: 'center' + } + }; + } + if (col >= 0 && col < table.colCount && row === table.rowCount - 2) { + return { + text: '统计数据中最低薪资:' + table.getCellOriginValue(table.colCount - 1, table.rowCount - 2), + range: { + start: { + col: 0, + row: table.rowCount - 2 + }, + end: { + col: table.colCount - 1, + row: table.rowCount - 2 + } + }, + style: { + textStick: true, + borderLineWidth: [6, 1, 1, 1], + borderColor: ['gray'], + textAlign: 'center' + } + }; + } } + // transpose: true // widthMode: 'adaptive' }; const tableInstance = new VTable.ListTable(option); + // tableInstance.updateFilterRules([ + // { + // filterKey: 'sex', + // filteredValues: ['boy'] + // } + // ]); window.tableInstance = tableInstance; tableInstance.on('change_cell_value', arg => { console.log(arg); diff --git a/packages/vtable/examples/pivot-analysis/pivot-analysis-aggregationRules.ts b/packages/vtable/examples/pivot-analysis/pivot-analysis-aggregationRules.ts index b5ad07791..448703cc2 100644 --- a/packages/vtable/examples/pivot-analysis/pivot-analysis-aggregationRules.ts +++ b/packages/vtable/examples/pivot-analysis/pivot-analysis-aggregationRules.ts @@ -24,7 +24,9 @@ export function createTable() { indicatorKey: '销售总额', //指标名称 field: 'sales', //指标依据字段 aggregationType: VTable.TYPES.AggregationType.SUM, //计算类型 - formatFun: sumNumberFormat + formatFun(value, col, row, table) { + return value; + } }, { indicatorKey: '订单数', //指标名称 diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 8dfb34662..0ff82c0db 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -20,7 +20,7 @@ import { SimpleHeaderLayoutMap } from './layout'; import { isValid } from '@visactor/vutils'; import { _setDataSource, _setRecords, sortRecords } from './core/tableHelper'; import { BaseTable } from './core'; -import type { ListTableProtected } from './ts-types/base-table'; +import type { BaseTableAPI, ListTableProtected } from './ts-types/base-table'; import { TABLE_EVENT_TYPE } from './core/TABLE_EVENT_TYPE'; import { Title } from './components/title/title'; import { cloneDeep } from '@visactor/vutils'; @@ -114,6 +114,14 @@ export class ListTable extends BaseTable implements ListTableAPI { get sortState(): SortState | SortState[] { return this.internalProps.sortState; } + + get records() { + return this.dataSource.source; + } + + get recordsCount() { + return this.dataSource.source.length; + } // /** // * Gets the define of the header. // */ @@ -209,10 +217,10 @@ export class ListTable extends BaseTable implements ListTableAPI { } else if (table.internalProps.layoutMap.isAggregation(col, row)) { if (table.internalProps.layoutMap.isTopAggregation(col, row)) { const aggregator = table.internalProps.layoutMap.getAggregatorOnTop(col, row); - return aggregator?.value(); + return aggregator?.formatValue ? aggregator.formatValue(col, row, this as BaseTableAPI) : ''; } else if (table.internalProps.layoutMap.isBottomAggregation(col, row)) { const aggregator = table.internalProps.layoutMap.getAggregatorOnBottom(col, row); - return aggregator?.value(); + return aggregator?.formatValue ? aggregator.formatValue(col, row, this as BaseTableAPI) : ''; } } const { field, fieldFormat } = table.internalProps.layoutMap.getBody(col, row); diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index 2bb0a9a8d..2acd90d5e 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -180,6 +180,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { } = {}; // columns对应各个字段的聚合类对象 fieldAggregators: Aggregator[] = []; + layoutColumnObjects: ColumnData[] = []; constructor( dataSourceObj?: DataSourceParam | DataSource, dataConfig?: IListTableDataConfig, @@ -192,7 +193,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { this.dataSourceObj = dataSourceObj; this.dataConfig = dataConfig; this._get = dataSourceObj?.get.bind(dataSourceObj) || (undefined as any); - columnObjs && this._generateFieldAggragations(columnObjs); + this.layoutColumnObjects = columnObjs; this._source = this.processRecords(dataSourceObj?.source ?? dataSourceObj); this._sourceLength = this._source?.length || 0; this.sortedIndexMap = new Map(); @@ -248,8 +249,10 @@ export class DataSource extends EventTarget implements DataSourceAPI { this.registerAggregator(AggregationType.AVG, AvgAggregator); this.registerAggregator(AggregationType.NONE, NoneAggregator); } - _generateFieldAggragations(columnObjs: ColumnData[]) { - for (let i = 0; i < columnObjs.length; i++) { + _generateFieldAggragations() { + const columnObjs = this.layoutColumnObjects; + for (let i = 0; i < columnObjs?.length; i++) { + columnObjs[i].aggregator = null; //重置聚合器 如更新了过滤条件都需要重新计算 const field = columnObjs[i].field; const aggragation = columnObjs[i].aggregation; if (!aggragation) { @@ -258,7 +261,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { if (Array.isArray(aggragation)) { for (let j = 0; j < aggragation.length; j++) { const item = aggragation[j]; - const aggregator = new this.registedAggregators[item.aggregationType](field as string); + const aggregator = new this.registedAggregators[item.aggregationType](field as string, item.formatFun); this.fieldAggregators.push(aggregator); if (!columnObjs[i].aggregator) { columnObjs[i].aggregator = []; @@ -266,13 +269,17 @@ export class DataSource extends EventTarget implements DataSourceAPI { columnObjs[i].aggregator.push(aggregator); } } else { - const aggregator = new this.registedAggregators[aggragation.aggregationType](field as string); + const aggregator = new this.registedAggregators[aggragation.aggregationType]( + field as string, + aggragation.formatFun + ); this.fieldAggregators.push(aggregator); columnObjs[i].aggregator = aggregator; } } } processRecords(records: any[]) { + this._generateFieldAggragations(); const filteredRecords = []; const isHasAggregation = this.fieldAggregators.length >= 1; const isHasFilterRule = this.dataConfig?.filterRules?.length >= 1; diff --git a/packages/vtable/src/layout/layout-helper.ts b/packages/vtable/src/layout/layout-helper.ts index 1cfa3cb18..830f627ec 100644 --- a/packages/vtable/src/layout/layout-helper.ts +++ b/packages/vtable/src/layout/layout-helper.ts @@ -20,11 +20,12 @@ export function checkHasAggregationOnTop(layoutMap: SimpleHeaderLayoutMap) { const column = columnObjects[i]; if ((column as ColumnData)?.aggregation) { if (Array.isArray((column as ColumnData)?.aggregation)) { - count = ((column as ColumnData).aggregation as Array).filter( - item => item.showOnTop === true - ).length; + count = Math.max( + count, + ((column as ColumnData).aggregation as Array).filter(item => item.showOnTop === true).length + ); } else if (((column as ColumnData).aggregation as Aggregation).showOnTop === true) { - count = 1; + count = Math.max(count, 1); } } } @@ -38,11 +39,12 @@ export function checkHasAggregationOnBottom(layoutMap: SimpleHeaderLayoutMap) { const column = columnObjects[i]; if ((column as ColumnData)?.aggregation) { if (Array.isArray((column as ColumnData)?.aggregation)) { - count = ((column as ColumnData).aggregation as Array).filter( - item => item.showOnTop === false - ).length; + count = Math.max( + count, + ((column as ColumnData).aggregation as Array).filter(item => item.showOnTop === false).length + ); } else if (((column as ColumnData).aggregation as Aggregation).showOnTop === false) { - count = 1; + count = Math.max(count, 1); } } } diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 323df91e7..ac3397c51 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -96,12 +96,13 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { if (this.hasAggregation) { if (this.hasAggregationOnBottomCount) { if (this.transpose) { - if (col === this.colCount - this.hasAggregationOnBottomCount) { + if (col >= this.colCount - this.hasAggregationOnBottomCount) { + return true; + } + } else { + if (row >= this.rowCount - this.hasAggregationOnBottomCount) { return true; } - } - if (row >= this.rowCount - this.hasAggregationOnBottomCount) { - return true; } } if (this.hasAggregationOnTopCount) { @@ -109,9 +110,10 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { if (col >= this.rowHeaderLevelCount && col < this.rowHeaderLevelCount + this.hasAggregationOnTopCount) { return true; } - } - if (row >= this.columnHeaderLevelCount && row < this.columnHeaderLevelCount + this.hasAggregationOnTopCount) { - return true; + } else { + if (row >= this.columnHeaderLevelCount && row < this.columnHeaderLevelCount + this.hasAggregationOnTopCount) { + return true; + } } } } @@ -123,9 +125,10 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { if (col >= this.rowHeaderLevelCount && col < this.rowHeaderLevelCount + this.hasAggregationOnTopCount) { return true; } - } - if (row >= this.columnHeaderLevelCount && row < this.columnHeaderLevelCount + this.hasAggregationOnTopCount) { - return true; + } else { + if (row >= this.columnHeaderLevelCount && row < this.columnHeaderLevelCount + this.hasAggregationOnTopCount) { + return true; + } } } return false; @@ -133,12 +136,13 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { isBottomAggregation(col: number, row: number): boolean { if (this.hasAggregationOnBottomCount) { if (this.transpose) { - if (col === this.colCount - this.hasAggregationOnBottomCount) { + if (col >= this.colCount - this.hasAggregationOnBottomCount) { + return true; + } + } else { + if (row >= this.rowCount - this.hasAggregationOnBottomCount) { return true; } - } - if (row >= this.rowCount - this.hasAggregationOnBottomCount) { - return true; } } return false; diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 9ab7cc9c1..9459ad1b8 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -370,6 +370,8 @@ export interface BaseTableConstructorOptions { resizeTime?: number; } export interface BaseTableAPI { + /** 数据总条目数 */ + recordsCount: number; /** 表格的行数 */ rowCount: number; /** 表格的列数 */ diff --git a/packages/vtable/src/ts-types/new-data-set.ts b/packages/vtable/src/ts-types/new-data-set.ts index efc73dc50..bec776993 100644 --- a/packages/vtable/src/ts-types/new-data-set.ts +++ b/packages/vtable/src/ts-types/new-data-set.ts @@ -1,5 +1,5 @@ import type { Either } from '../tools/helper'; -import type { SortOrder } from './common'; +import type { BaseTableAPI } from './base-table'; //#region 总计小计 export interface TotalsStatus { @@ -138,7 +138,7 @@ export interface AggregationRule { field: T extends AggregationType.RECORD ? string[] | string : string; aggregationType: T; /**计算结果格式化 */ - formatFun?: (num: number) => string; + formatFun?: (value: number, col: number, row: number, table: BaseTableAPI) => number | string; } export type AggregationRules = AggregationRule[]; //#endregion 聚合规则 @@ -231,7 +231,8 @@ export type CollectedValue = { max?: number; min?: number } | Array; //#region 提供给基本表格的类型 export type Aggregation = { - aggregationType: AggregationType; + aggregationType?: AggregationType; showOnTop?: boolean; + formatFun?: (value: number, col: number, row: number, table: BaseTableAPI) => string | number; }; //#endregion From 3a635f88a662af98d65e666be577ee07d30655c5 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 6 Feb 2024 18:23:39 +0800 Subject: [PATCH 11/63] feat: list table aggregation support global option --- .../list-analysis/list-aggregation-global.ts | 258 ++++++++++++++++++ .../list-analysis/list-aggregation.ts | 2 +- packages/vtable/examples/menu.ts | 4 + .../vtable/src/layout/simple-header-layout.ts | 38 ++- packages/vtable/src/ts-types/table-engine.ts | 6 +- 5 files changed, 296 insertions(+), 12 deletions(-) create mode 100644 packages/vtable/examples/list-analysis/list-aggregation-global.ts diff --git a/packages/vtable/examples/list-analysis/list-aggregation-global.ts b/packages/vtable/examples/list-analysis/list-aggregation-global.ts new file mode 100644 index 000000000..10f8cd290 --- /dev/null +++ b/packages/vtable/examples/list-analysis/list-aggregation-global.ts @@ -0,0 +1,258 @@ +import * as VTable from '../../src'; +import { AggregationType } from '../../src/ts-types'; +const CONTAINER_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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + salary: Math.round(Math.random() * 10000) + })); +}; + +export function createTable() { + const records = generatePersons(300); + const columns: VTable.ColumnsDefine = [ + { + field: '', + title: '行号', + width: 80, + fieldFormat(data, col, row, table) { + return row - 1; + } + }, + { + field: 'id', + title: 'ID', + width: '1%', + minWidth: 200, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'salary', + title: 'salary', + width: 100 + }, + { + field: 'salary', + title: 'salary', + width: 100, + aggregation: [ + { + aggregationType: AggregationType.MAX, + formatFun(value) { + return '最高薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: AggregationType.MIN, + formatFun(value) { + return '最低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ] + } + ]; + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records, + sortState: { + field: 'id', + order: 'desc' + }, + // dataConfig: { + // filterRules: [ + // { + // filterFunc: (record: Record) => { + // return record.id % 2 === 0; + // } + // } + // ] + // }, + columns, + tooltip: { + isShowOverflowTextTooltip: true + }, + // frozenColCount: 1, + bottomFrozenRowCount: 3, + rightFrozenColCount: 1, + overscrollBehavior: 'none', + autoWrapText: true, + widthMode: 'autoWidth', + heightMode: 'autoHeight', + dragHeaderMode: 'all', + keyboardOptions: { + pasteValueToCell: true + }, + eventOptions: { + preventDefaultContextMenu: false + }, + pagination: { + perPageCount: 100, + currentPage: 0 + }, + theme: VTable.themes.DEFAULT.extends({ + cornerRightBottomCellStyle: { + bgColor: 'rgba(1,1,1,0.1)', + borderColor: 'red' + } + }), + // customMergeCell: (col, row, table) => { + // if (col >= 0 && col < table.colCount && row === table.rowCount - 1) { + // return { + // text: '统计数据中平均薪资:' + table.getCellOriginValue(table.colCount - 1, table.rowCount - 1), + // range: { + // start: { + // col: 0, + // row: table.rowCount - 1 + // }, + // end: { + // col: table.colCount - 1, + // row: table.rowCount - 1 + // } + // }, + // style: { + // borderLineWidth: [6, 1, 1, 1], + // borderColor: ['gray'], + // textAlign: 'center' + // } + // }; + // } + // if (col >= 0 && col < table.colCount && row === table.rowCount - 2) { + // return { + // text: '统计数据中最低薪资:' + table.getCellOriginValue(table.colCount - 1, table.rowCount - 2), + // range: { + // start: { + // col: 0, + // row: table.rowCount - 2 + // }, + // end: { + // col: table.colCount - 1, + // row: table.rowCount - 2 + // } + // }, + // style: { + // textStick: true, + // borderLineWidth: [6, 1, 1, 1], + // borderColor: ['gray'], + // textAlign: 'center' + // } + // }; + // } + // }, + aggregation(args) { + if (args.col === 1) { + return [ + { + aggregationType: AggregationType.MAX, + formatFun(value) { + return '最大ID:' + Math.round(value) + '号'; + } + }, + { + aggregationType: AggregationType.MIN, + showOnTop: false, + formatFun(value, col, row, table) { + return '最小ID:' + Math.round(value) + '号'; + } + } + ]; + } + if (args.field === 'salary') { + return [ + { + aggregationType: AggregationType.MIN, + formatFun(value) { + return '最低低低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均平均平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ]; + } + return null; + } + // transpose: true + // widthMode: 'adaptive' + }; + const tableInstance = new VTable.ListTable(option); + // tableInstance.updateFilterRules([ + // { + // filterKey: 'sex', + // filteredValues: ['boy'] + // } + // ]); + window.tableInstance = tableInstance; + tableInstance.on('change_cell_value', arg => { + console.log(arg); + }); +} diff --git a/packages/vtable/examples/list-analysis/list-aggregation.ts b/packages/vtable/examples/list-analysis/list-aggregation.ts index b6907505b..813ce0740 100644 --- a/packages/vtable/examples/list-analysis/list-aggregation.ts +++ b/packages/vtable/examples/list-analysis/list-aggregation.ts @@ -151,7 +151,7 @@ export function createTable() { rightFrozenColCount: 1, overscrollBehavior: 'none', autoWrapText: true, - // widthMode: 'autoWidth', + widthMode: 'autoWidth', heightMode: 'autoHeight', dragHeaderMode: 'all', keyboardOptions: { diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 0bc2746bf..b8f031e1d 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -107,6 +107,10 @@ export const menus = [ { path: 'list-analysis', name: 'list-aggregation' + }, + { + path: 'list-analysis', + name: 'list-aggregation-global' } ] }, diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 248df09d1..1af0985e1 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -831,16 +831,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { define: colDef, columnWidthComputeMode: colDef.columnWidthComputeMode, disableColumnResize: colDef?.disableColumnResize, - aggregation: colDef.aggregation - ? Array.isArray(colDef.aggregation) - ? colDef.aggregation.map(item => { - if (!isValid(item.showOnTop)) { - item.showOnTop = false; - } - return item; - }) - : Object.assign({ showOnTop: false }, colDef.aggregation) - : undefined + aggregation: this._getAggregationForColumn(colDef, col) }); for (let r = row + 1; r < this._headerCellIds.length; r++) { this._headerCellIds[r][col] = id; @@ -849,6 +840,33 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { }); return results; } + private _getAggregationForColumn(colDef: ColumnDefine, col: number) { + let aggregation; + if (colDef.aggregation) { + aggregation = colDef.aggregation; + } else if (this._table.options.aggregation) { + if (typeof this._table.options.aggregation === 'function') { + aggregation = this._table.options.aggregation({ + col: col, + field: colDef.field as string + }); + } else { + aggregation = this._table.options.aggregation; + } + } + if (aggregation) { + if (Array.isArray(aggregation)) { + return aggregation.map(item => { + if (!isValid(item.showOnTop)) { + item.showOnTop = false; + } + return item; + }); + } + return Object.assign({ showOnTop: false }, aggregation); + } + return null; + } private _newRow(row: number, hideColumnsSubHeader = false): number[] { //如果当前行已经有数组对象 将上一行的id内容补全到当前行上 if (this._headerCellIds[row]) { diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index ef43a3516..06199070a 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -4,7 +4,7 @@ export type { HeaderData } from './list-table/layout-map/api'; export type LayoutObjectId = number | string; import type { Rect } from '../tools/Rect'; import type { BaseTableAPI, BaseTableConstructorOptions, ListTableProtected } from './base-table'; -import type { FilterRules, IListTableDataConfig, IPivotTableDataConfig } from './new-data-set'; +import type { Aggregation, FilterRules, IListTableDataConfig, IPivotTableDataConfig } from './new-data-set'; import type { Either } from '../tools/helper'; import type { IChartIndicator, @@ -176,6 +176,10 @@ export interface ListTableConstructorOptions extends BaseTableConstructorOptions * "fixedFrozenCount"(可调整冻结列,并维持冻结数量不变):允许自由拖拽其他列的表头移入或移出冻结列位置,同时保持冻结列的数量不变。 */ frozenColDragHeaderMode?: 'disabled' | 'adjustFrozenCount' | 'fixedFrozenCount'; + aggregation?: + | Aggregation + | Aggregation[] + | ((args: { col: number; field: string }) => Aggregation | Aggregation[] | null); } export interface ListTableAPI extends BaseTableAPI { From 81ee82add2ef1c2dde24a75aaaeb81a4951be394 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 7 Feb 2024 18:38:11 +0800 Subject: [PATCH 12/63] feat: when edit cell value the aggregator value recalcute --- .../list-analysis/list-aggregation-edit.ts | 224 ++++++++++++++++++ .../list-analysis/list-aggregation-global.ts | 13 +- packages/vtable/examples/menu.ts | 4 + packages/vtable/src/ListTable.ts | 21 ++ .../vtable/src/dataset/statistics-helper.ts | 80 +++++++ .../vtable/src/layout/simple-header-layout.ts | 36 +++ 6 files changed, 375 insertions(+), 3 deletions(-) create mode 100644 packages/vtable/examples/list-analysis/list-aggregation-edit.ts diff --git a/packages/vtable/examples/list-analysis/list-aggregation-edit.ts b/packages/vtable/examples/list-analysis/list-aggregation-edit.ts new file mode 100644 index 000000000..694ffa2f2 --- /dev/null +++ b/packages/vtable/examples/list-analysis/list-aggregation-edit.ts @@ -0,0 +1,224 @@ +import * as VTable from '../../src'; +import { AggregationType } from '../../src/ts-types'; +import { InputEditor } from '@visactor/vtable-editors'; +const CONTAINER_ID = 'vTable'; +const input_editor = new InputEditor({}); +VTable.register.editor('input', input_editor); +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + salary: Math.round(Math.random() * 10000) + })); +}; + +export function createTable() { + const records = generatePersons(300); + const columns: VTable.ColumnsDefine = [ + { + field: '', + title: '行号', + width: 80, + fieldFormat(data, col, row, table) { + return row - 1; + }, + aggregation: { + aggregationType: AggregationType.NONE, + formatFun() { + return '汇总:'; + } + } + }, + { + field: 'id', + title: 'ID', + width: '1%', + minWidth: 200, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'salary', + title: 'salary', + width: 100 + }, + { + field: 'salary', + title: 'salary', + width: 100, + aggregation: [ + { + aggregationType: AggregationType.MAX, + // showOnTop: true, + formatFun(value) { + return '最高薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: AggregationType.MIN, + formatFun(value) { + return '最低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ] + } + ]; + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records, + // dataConfig: { + // filterRules: [ + // { + // filterFunc: (record: Record) => { + // return record.id % 2 === 0; + // } + // } + // ] + // }, + columns, + tooltip: { + isShowOverflowTextTooltip: true + }, + frozenColCount: 1, + bottomFrozenRowCount: 3, + rightFrozenColCount: 1, + overscrollBehavior: 'none', + autoWrapText: true, + widthMode: 'autoWidth', + heightMode: 'autoHeight', + dragHeaderMode: 'all', + keyboardOptions: { + pasteValueToCell: true + }, + eventOptions: { + preventDefaultContextMenu: false + }, + pagination: { + perPageCount: 100, + currentPage: 0 + }, + theme: VTable.themes.DEFAULT.extends({ + bottomFrozenStyle: { + bgColor: '#ECF1F5', + borderLineWidth: [6, 0, 1, 0], + borderColor: ['gray'] + } + }), + editor: 'input', + headerEditor: 'input', + aggregation(args) { + if (args.col === 1) { + return [ + { + aggregationType: AggregationType.MAX, + formatFun(value) { + return '最大ID:' + Math.round(value) + '号'; + } + }, + { + aggregationType: AggregationType.MIN, + showOnTop: false, + formatFun(value, col, row, table) { + return '最小ID:' + Math.round(value) + '号'; + } + } + ]; + } + if (args.field === 'salary') { + return [ + { + aggregationType: AggregationType.MIN, + formatFun(value) { + return '最低低低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均平均平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ]; + } + return null; + } + // transpose: true + // widthMode: 'adaptive' + }; + const tableInstance = new VTable.ListTable(option); + // tableInstance.updateFilterRules([ + // { + // filterKey: 'sex', + // filteredValues: ['boy'] + // } + // ]); + window.tableInstance = tableInstance; + tableInstance.on('change_cell_value', arg => { + console.log(arg); + }); +} diff --git a/packages/vtable/examples/list-analysis/list-aggregation-global.ts b/packages/vtable/examples/list-analysis/list-aggregation-global.ts index 10f8cd290..19aa352de 100644 --- a/packages/vtable/examples/list-analysis/list-aggregation-global.ts +++ b/packages/vtable/examples/list-analysis/list-aggregation-global.ts @@ -25,6 +25,12 @@ export function createTable() { width: 80, fieldFormat(data, col, row, table) { return row - 1; + }, + aggregation: { + aggregationType: AggregationType.NONE, + formatFun() { + return '汇总:'; + } } }, { @@ -156,9 +162,10 @@ export function createTable() { currentPage: 0 }, theme: VTable.themes.DEFAULT.extends({ - cornerRightBottomCellStyle: { - bgColor: 'rgba(1,1,1,0.1)', - borderColor: 'red' + bottomFrozenStyle: { + bgColor: '#ECF1F5', + borderLineWidth: [6, 0, 1, 0], + borderColor: ['gray'] } }), // customMergeCell: (col, row, table) => { diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index b8f031e1d..4bcd46491 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -111,6 +111,10 @@ export const menus = [ { path: 'list-analysis', name: 'list-aggregation-global' + }, + { + path: 'list-analysis', + name: 'list-aggregation-edit' } ] }, diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index e4096fb1e..0afb9f967 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -961,6 +961,27 @@ export class ListTable extends BaseTable implements ListTableAPI { } else { this.dataSource.changeFieldValue(value, recordIndex, field, col, row, this); } + //改变单元格的值后 聚合值做重新计算 + const aggregators = this.internalProps.layoutMap.getAggregators(col, row); + if (aggregators) { + if (Array.isArray(aggregators)) { + for (let i = 0; i < aggregators?.length; i++) { + aggregators[i].recalculate(); + } + } else { + aggregators.recalculate(); + } + const aggregatorCells = this.internalProps.layoutMap.getCellAddressHasAggregator(col, row); + for (let i = 0; i < aggregatorCells.length; i++) { + const range = this.getCellRange(aggregatorCells[i].col, aggregatorCells[i].row); + for (let sCol = range.start.col; sCol <= range.end.col; sCol++) { + for (let sRow = range.start.row; sRow <= range.end.row; sRow++) { + this.scenegraph.updateCellContent(sCol, sRow); + } + } + } + } + // const cell_value = this.getCellValue(col, row); const range = this.getCellRange(col, row); for (let sCol = range.start.col; sCol <= range.end.col; sCol++) { diff --git a/packages/vtable/src/dataset/statistics-helper.ts b/packages/vtable/src/dataset/statistics-helper.ts index 4359bf24f..cccd2819c 100644 --- a/packages/vtable/src/dataset/statistics-helper.ts +++ b/packages/vtable/src/dataset/statistics-helper.ts @@ -30,6 +30,7 @@ export abstract class Aggregator { // } abstract push(record: any): void; abstract value(): any; + abstract recalculate(): any; clearCacheValue() { this._formatedValue = undefined; } @@ -65,6 +66,9 @@ export class RecordAggregator extends Aggregator { reset() { this.records = []; } + recalculate() { + // do nothing + } } export class NoneAggregator extends Aggregator { @@ -85,6 +89,9 @@ export class NoneAggregator extends Aggregator { this.records = []; this.fieldValue = undefined; } + recalculate() { + // do nothing + } } export class SumAggregator extends Aggregator { type: string = AggregationType.SUM; @@ -135,6 +142,34 @@ export class SumAggregator extends Aggregator { this.records = []; this.sum = 0; } + recalculate() { + this.sum = 0; + this._formatedValue = undefined; + for (let i = 0; i < this.records.length; i++) { + const record = this.records[i]; + if (record.className === 'Aggregator') { + const value = record.value(); + this.sum += value; + if (this.needSplitPositiveAndNegativeForSum) { + if (value > 0) { + this.positiveSum += value; + } else if (value < 0) { + this.nagetiveSum += value; + } + } + } else if (!isNaN(parseFloat(record[this.field]))) { + const value = parseFloat(record[this.field]); + this.sum += value; + if (this.needSplitPositiveAndNegativeForSum) { + if (value > 0) { + this.positiveSum += value; + } else if (value < 0) { + this.nagetiveSum += value; + } + } + } + } + } } export class CountAggregator extends Aggregator { @@ -192,6 +227,21 @@ export class AvgAggregator extends Aggregator { this.sum = 0; this.count = 0; } + recalculate() { + this.sum = 0; + this.count = 0; + this._formatedValue = undefined; + for (let i = 0; i < this.records.length; i++) { + const record = this.records[i]; + if (record.className === 'Aggregator' && record.type === AggregationType.AVG) { + this.sum += record.sum; + this.count += record.count; + } else if (!isNaN(parseFloat(record[this.field]))) { + this.sum += parseFloat(record[this.field]); + this.count++; + } + } + } } export class MaxAggregator extends Aggregator { type: string = AggregationType.MAX; @@ -222,6 +272,22 @@ export class MaxAggregator extends Aggregator { this.records = []; this.max = Number.MIN_SAFE_INTEGER; } + recalculate() { + this.max = Number.MIN_SAFE_INTEGER; + this._formatedValue = undefined; + for (let i = 0; i < this.records.length; i++) { + const record = this.records[i]; + if (record.className === 'Aggregator') { + this.max = record.max > this.max ? record.max : this.max; + } else if (typeof record === 'number') { + this.max = record > this.max ? record : this.max; + } else if (typeof record[this.field] === 'number') { + this.max = record[this.field] > this.max ? record[this.field] : this.max; + } else if (!isNaN(record[this.field])) { + this.max = parseFloat(record[this.field]) > this.max ? parseFloat(record[this.field]) : this.max; + } + } + } } export class MinAggregator extends Aggregator { type: string = AggregationType.MIN; @@ -251,6 +317,20 @@ export class MinAggregator extends Aggregator { this.records = []; this.min = Number.MAX_SAFE_INTEGER; } + recalculate() { + this.min = Number.MAX_SAFE_INTEGER; + this._formatedValue = undefined; + for (let i = 0; i < this.records.length; i++) { + const record = this.records[i]; + if (record.className === 'Aggregator') { + this.min = record.min < this.min ? record.min : this.min; + } else if (typeof record === 'number') { + this.min = record < this.min ? record : this.min; + } else if (typeof record[this.field] === 'number') { + this.min = record[this.field] < this.min ? record[this.field] : this.min; + } + } + } } export function indicatorSort(a: any, b: any) { if (a && b) { diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 1af0985e1..9735f5207 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -163,6 +163,11 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { return this._hasAggregationOnBottomCount; } + getAggregators(col: number, row: number) { + const column = this.getBody(col, row); + const aggregators = column.aggregator; + return aggregators; + } getAggregatorOnTop(col: number, row: number) { const column = this.getBody(col, row); const aggregators = column.aggregator; @@ -212,6 +217,37 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { } return null; } + /** + * 获取单元格所在行或者列中的聚合值的单元格地址 + * @param col + * @param row + * @returns + */ + getCellAddressHasAggregator(col: number, row: number) { + const cellAddrs = []; + if (this.transpose) { + const topCount = this.hasAggregationOnTopCount; + for (let i = 0; i < topCount; i++) { + cellAddrs.push({ col: this.headerLevelCount + i, row }); + } + + const bottomCount = this.hasAggregationOnBottomCount; + for (let i = 0; i < bottomCount; i++) { + cellAddrs.push({ col: this.rowCount - bottomCount + i, row }); + } + } else { + const topCount = this.hasAggregationOnTopCount; + for (let i = 0; i < topCount; i++) { + cellAddrs.push({ col, row: this.headerLevelCount + i }); + } + + const bottomCount = this.hasAggregationOnBottomCount; + for (let i = 0; i < bottomCount; i++) { + cellAddrs.push({ col, row: this.rowCount - bottomCount + i }); + } + } + return cellAddrs; + } getCellLocation(col: number, row: number): CellLocation { if (this.isHeader(col, row)) { if (this.transpose) { From d946904ce11b7cd83744bb5b9214f08cb0536cf2 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 7 Feb 2024 19:13:55 +0800 Subject: [PATCH 13/63] feat: when edit cell value the aggregator value recalcute --- packages/vtable/src/dataset/statistics-helper.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/vtable/src/dataset/statistics-helper.ts b/packages/vtable/src/dataset/statistics-helper.ts index cccd2819c..60c6ff3e2 100644 --- a/packages/vtable/src/dataset/statistics-helper.ts +++ b/packages/vtable/src/dataset/statistics-helper.ts @@ -197,6 +197,18 @@ export class CountAggregator extends Aggregator { this.records = []; this.count = 0; } + recalculate() { + this.count = 0; + this._formatedValue = undefined; + for (let i = 0; i < this.records.length; i++) { + const record = this.records[i]; + if (record.className === 'Aggregator') { + this.count += record.value(); + } else { + this.count++; + } + } + } } export class AvgAggregator extends Aggregator { type: string = AggregationType.AVG; From 1080133252aa3709920db8d1243c9cce2095ec0a Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 7 Feb 2024 19:21:19 +0800 Subject: [PATCH 14/63] refactor: rename hasCustomRenderOrLayout to private --- packages/vtable/src/ListTable.ts | 2 +- packages/vtable/src/PivotChart.ts | 2 +- packages/vtable/src/PivotTable.ts | 2 +- packages/vtable/src/core/BaseTable.ts | 2 +- packages/vtable/src/scenegraph/scenegraph.ts | 4 ++-- packages/vtable/src/ts-types/base-table.ts | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 0afb9f967..072acac72 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -1432,7 +1432,7 @@ export class ListTable extends BaseTable implements ListTableAPI { } } - hasCustomRenderOrLayout() { + _hasCustomRenderOrLayout() { const { headerObjects } = this.internalProps.layoutMap; if (this.options.customRender) { return true; diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index d303e388d..687264418 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -1224,7 +1224,7 @@ export class PivotChart extends BaseTable implements PivotChartAPI { this.eventManager.updateEventBinder(); } - hasCustomRenderOrLayout() { + _hasCustomRenderOrLayout() { if (this.options.customRender) { return true; } diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 045ee97c1..824784067 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -1225,7 +1225,7 @@ export class PivotTable extends BaseTable implements PivotTableAPI { this.records[rowIndex][colIndex] = newValue; } } - hasCustomRenderOrLayout() { + _hasCustomRenderOrLayout() { if (this.options.customRender) { return true; } diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index a27c8516b..42bd70de3 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -2430,7 +2430,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { */ abstract updatePagination(pagination: IPagination): void; - abstract hasCustomRenderOrLayout(): boolean; + abstract _hasCustomRenderOrLayout(): boolean; get recordsCount() { return this.records?.length; diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index e589c20c6..1f1117ac1 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -217,7 +217,7 @@ export class Scenegraph { */ clearCells() { // unbind AutoPoptip - if (this.table.isPivotChart() || this.table.hasCustomRenderOrLayout()) { + if (this.table.isPivotChart() || this.table._hasCustomRenderOrLayout()) { // bind for axis label in pivotChart this.stage.pluginService.findPluginsByName('poptipForText').forEach(plugin => { plugin.deactivate(this.stage.pluginService); @@ -337,7 +337,7 @@ export class Scenegraph { */ createSceneGraph() { // bind AutoPoptip - if (this.table.isPivotChart() || this.table.hasCustomRenderOrLayout()) { + if (this.table.isPivotChart() || this.table._hasCustomRenderOrLayout()) { // bind for axis label in pivotChart (this.stage.pluginService as any).autoEnablePlugins.getContributions().forEach((p: any) => { if (p.name === 'poptipForText') { diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 64e669eb7..8fc99dc51 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -688,7 +688,7 @@ export interface BaseTableAPI { /** 获取表格body部分的显示行号范围 */ getBodyVisibleRowRange: () => { rowStart: number; rowEnd: number }; - hasCustomRenderOrLayout: () => boolean; + _hasCustomRenderOrLayout: () => boolean; /** 根据表格单元格的行列号 获取在body部分的列索引及行索引 */ getBodyIndexByTableIndex: (col: number, row: number) => CellAddress; /** 根据body部分的列索引及行索引,获取单元格的行列号 */ From bf41e1977184da331c694c13a8e9948c033c7241 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 7 Feb 2024 19:55:46 +0800 Subject: [PATCH 15/63] feat: add api getAggregateValuesByField --- packages/vtable/src/ListTable.ts | 47 +++++++++++++++++++ .../vtable/src/layout/simple-header-layout.ts | 13 +++++ 2 files changed, 60 insertions(+) diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 072acac72..32337ab3a 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -1,4 +1,5 @@ import type { + AggregationType, CellAddress, CellRange, ColumnsDefine, @@ -32,6 +33,7 @@ import { computeColWidth } from './scenegraph/layout/compute-col-width'; import { computeRowHeight } from './scenegraph/layout/compute-row-height'; import { defaultOrderFn } from './tools/util'; import type { IEditor } from '@visactor/vtable-editors'; +import type { ColumnData } from './ts-types/list-table/layout-map/api'; export class ListTable extends BaseTable implements ListTableAPI { declare internalProps: ListTableProtected; @@ -1451,4 +1453,49 @@ export class ListTable extends BaseTable implements ListTableAPI { } return false; } + /** + * 根据字段获取聚合值 + * @param field 字段名 + * 返回数组,包括列号和每一列的聚合值数组 + */ + getAggregateValuesByField(field: string | number): { + col: number; + aggregateValue: { aggregationType: AggregationType; value: number | string }[]; + }[] { + const columns = this.internalProps.layoutMap.getColumnByField(field); + const results: { + col: number; + aggregateValue: { aggregationType: AggregationType; value: number | string }[]; + }[] = []; + for (let i = 0; i < columns.length; i++) { + const aggregator = columns[i].columnDefine.aggregator; + delete columns[i].columnDefine; + if (aggregator) { + const columnAggregateValue: { + col: number; + aggregateValue: { aggregationType: AggregationType; value: number | string }[]; + } = { + col: columns[i].col, + aggregateValue: null + }; + columnAggregateValue.aggregateValue = []; + if (Array.isArray(aggregator)) { + for (let j = 0; j < aggregator.length; j++) { + columnAggregateValue.aggregateValue.push({ + aggregationType: aggregator[j].type as AggregationType, + value: aggregator[j].value() + }); + } + } else { + columnAggregateValue.aggregateValue.push({ + aggregationType: aggregator.type as AggregationType, + value: aggregator.value() + }); + } + + results.push(columnAggregateValue); + } + } + return results; + } } diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 9735f5207..673051670 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -1149,4 +1149,17 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { define.title = title; define.define.title = title; } + + getColumnByField(field: string | number): { + col: number; + columnDefine: ColumnData; + }[] { + const result = this.columnObjects?.reduce((pre: { col: number; columnDefine: ColumnData }[], cur, index) => { + if (cur.field === field) { + pre.push({ col: index, columnDefine: cur }); + } + return pre; + }, []); + return result; + } } From e6504130e11c266168e541356a4e6164472ab7f8 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 7 Feb 2024 19:56:04 +0800 Subject: [PATCH 16/63] docs: update changlog of rush --- ...8-feature-listtable-caculate_2024-02-07-11-56.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-07-11-56.json diff --git a/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-07-11-56.json b/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-07-11-56.json new file mode 100644 index 000000000..1627da211 --- /dev/null +++ b/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-07-11-56.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: add api getAggregateValuesByField\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 94ebef426b894ace90320c82eb595a981bcdfa8d Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 7 Feb 2024 19:57:02 +0800 Subject: [PATCH 17/63] feat: add api getAggregateValuesByField --- packages/vtable/src/ts-types/table-engine.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index 06199070a..2957ab529 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -4,7 +4,7 @@ export type { HeaderData } from './list-table/layout-map/api'; export type LayoutObjectId = number | string; import type { Rect } from '../tools/Rect'; import type { BaseTableAPI, BaseTableConstructorOptions, ListTableProtected } from './base-table'; -import type { Aggregation, FilterRules, IListTableDataConfig, IPivotTableDataConfig } from './new-data-set'; +import type { Aggregation, AggregationType, FilterRules, IPivotTableDataConfig } from './new-data-set'; import type { Either } from '../tools/helper'; import type { IChartIndicator, @@ -214,6 +214,10 @@ export interface ListTableAPI extends BaseTableAPI { deleteRecords: (recordIndexs: number[]) => void; updateRecords: (records: any[], recordIndexs: number[]) => void; updateFilterRules: (filterRules: FilterRules) => void; + getAggregateValuesByField: (field: string | number) => { + col: number; + aggregateValue: { aggregationType: AggregationType; value: number | string }[]; + }[]; } export interface PivotTableConstructorOptions extends BaseTableConstructorOptions { /** From 0c289d7b1994a9fdd3d76dae7fadff96f6aa20d5 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 8 Feb 2024 17:50:25 +0800 Subject: [PATCH 18/63] feat: add custom aggregation --- .../examples/list-analysis/olympic-winners.ts | 213 ++++++++++++++++++ packages/vtable/examples/menu.ts | 4 + packages/vtable/src/data/DataSource.ts | 18 +- .../vtable/src/dataset/statistics-helper.ts | 56 +++-- .../list-table/define/basic-define.ts | 4 +- packages/vtable/src/ts-types/new-data-set.ts | 12 +- packages/vtable/src/ts-types/table-engine.ts | 16 +- 7 files changed, 298 insertions(+), 25 deletions(-) create mode 100644 packages/vtable/examples/list-analysis/olympic-winners.ts diff --git a/packages/vtable/examples/list-analysis/olympic-winners.ts b/packages/vtable/examples/list-analysis/olympic-winners.ts new file mode 100644 index 000000000..383f857fa --- /dev/null +++ b/packages/vtable/examples/list-analysis/olympic-winners.ts @@ -0,0 +1,213 @@ +/* eslint-disable */ +import * as VTable from '../../src'; +const CONTAINER_ID = 'vTable'; + +export function createTable() { + VTable.register.icon('filter', { + name: 'filter', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' + }); + + VTable.register.icon('filtered', { + name: 'filtered', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' + }); + fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') + .then(res => res.json()) + .then(data => { + const columns: VTable.ColumnDefine[] = [ + { + field: 'athlete', + title: 'athlete', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun(value) { + return 'Total:'; + } + } + }, + { + field: 'age', + title: 'age', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.AVG, + formatFun(value) { + return Math.round(value) + '(Avg)'; + } + } + }, + { + field: 'country', + title: 'country', + headerIcon: 'filter', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.CUSTOM, + aggregationFun(values, records: any[]) { + // 使用 reduce() 方法统计金牌数 + const goldMedalCountByCountry = records.reduce((acc, data) => { + const country = data.country; + const gold = data.gold; + + if (acc[country]) { + acc[country] += gold; + } else { + acc[country] = gold; + } + return acc; + }, {}); + + // 找出金牌数最多的国家 + let maxGoldMedals = 0; + let countryWithMaxGoldMedals = ''; + + for (const country in goldMedalCountByCountry) { + if (goldMedalCountByCountry[country] > maxGoldMedals) { + maxGoldMedals = goldMedalCountByCountry[country]; + countryWithMaxGoldMedals = country; + } + } + return { + country: countryWithMaxGoldMedals, + gold: maxGoldMedals + }; + }, + formatFun(value: any) { + return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; + } + } + }, + { field: 'year', title: 'year', headerIcon: 'filter' }, + { field: 'sport', title: 'sport', headerIcon: 'filter' }, + { + field: 'gold', + title: 'gold', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'silver', + title: 'silver', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'bronze', + title: 'bronze', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'total', + title: 'total', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + } + ]; + const option: VTable.ListTableConstructorOptions = { + columns, + records: data, + autoWrapText: true, + heightMode: 'autoHeight', + widthMode: 'autoWidth', + bottomFrozenRowCount: 1 + }; + const t0 = window.performance.now(); + const instance = new VTable.ListTable(document.getElementById(CONTAINER_ID)!, option); + window.tableInstance = instance; + const filterListValues = { + country: ['all', 'China', 'United States', 'Australia'], + year: ['all', '2004', '2008', '2012', '2016', '2020'], + sport: ['all', 'Swimming', 'Cycling', 'Biathlon', 'Short-Track Speed Skating', 'Nordic Combined'] + }; + let filterListSelectedValues = ''; + let lastFilterField; + instance.on('icon_click', args => { + const { col, row, name } = args; + if (name === 'filter') { + const field = instance.getHeaderField(col, row); + if (select && lastFilterField === field) { + removeFilterElement(); + lastFilterField = null; + } else if (!select || lastFilterField !== field) { + const rect = instance.getCellRelativeRect(col, row); + createFilterElement(filterListValues[field as string], filterListSelectedValues, field, rect); + lastFilterField = field; + } + } + }); + + let filterContainer = instance.getElement(); + let select; + function createFilterElement(values: string[], curValue: string, field, positonRect) { + // create select tag + select = document.createElement('select'); + select.setAttribute('type', 'text'); + select.style.position = 'absolute'; + select.style.padding = '4px'; + select.style.width = '100%'; + select.style.boxSizing = 'border-box'; + + // create option tags + let opsStr = ''; + values.forEach(item => { + opsStr += + item === curValue + ? `` + : ``; + }); + select.innerHTML = opsStr; + + filterContainer.appendChild(select); + + select.style.top = positonRect.top + positonRect.height + 'px'; + select.style.left = positonRect.left + 'px'; + select.style.width = positonRect.width + 'px'; + select.style.height = positonRect.height + 'px'; + select.addEventListener('change', () => { + filterListSelectedValues = select.value; + instance.updateFilterRules([ + { + filterKey: field, + filteredValues: select.value + } + ]); + removeFilterElement(); + }); + } + function removeFilterElement() { + filterContainer.removeChild(select); + select.removeEventListener('change', () => { + // this.successCallback(); + }); + select = null; + } + }); +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 4bcd46491..5713ee21e 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -115,6 +115,10 @@ export const menus = [ { path: 'list-analysis', name: 'list-aggregation-edit' + }, + { + path: 'list-analysis', + name: 'olympic-winners' } ] }, diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index 2acd90d5e..6211d3737 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -2,6 +2,7 @@ import * as sort from '../tools/sort'; import type { Aggregation, AggregationRule, + CustomAggregation, DataSourceAPI, FieldAssessor, FieldData, @@ -30,7 +31,8 @@ import { MaxAggregator, MinAggregator, AvgAggregator, - NoneAggregator + NoneAggregator, + CustomAggregator } from '../dataset/statistics-helper'; import type { ColumnData } from '../ts-types/list-table/layout-map/api'; @@ -175,7 +177,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { // 注册聚合类型 registedAggregators: { [key: string]: { - new (dimension: string | string[], formatFun?: any, isRecord?: boolean): Aggregator; + new (dimension: string | string[], formatFun?: any, isRecord?: boolean, aggregationFun?: Function): Aggregator; }; } = {}; // columns对应各个字段的聚合类对象 @@ -248,6 +250,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { this.registerAggregator(AggregationType.MIN, MinAggregator); this.registerAggregator(AggregationType.AVG, AvgAggregator); this.registerAggregator(AggregationType.NONE, NoneAggregator); + this.registerAggregator(AggregationType.CUSTOM, CustomAggregator); } _generateFieldAggragations() { const columnObjs = this.layoutColumnObjects; @@ -261,7 +264,12 @@ export class DataSource extends EventTarget implements DataSourceAPI { if (Array.isArray(aggragation)) { for (let j = 0; j < aggragation.length; j++) { const item = aggragation[j]; - const aggregator = new this.registedAggregators[item.aggregationType](field as string, item.formatFun); + const aggregator = new this.registedAggregators[item.aggregationType]( + field as string, + item.formatFun, + true, + (item as CustomAggregation).aggregationFun + ); this.fieldAggregators.push(aggregator); if (!columnObjs[i].aggregator) { columnObjs[i].aggregator = []; @@ -271,7 +279,9 @@ export class DataSource extends EventTarget implements DataSourceAPI { } else { const aggregator = new this.registedAggregators[aggragation.aggregationType]( field as string, - aggragation.formatFun + aggragation.formatFun, + true, + (aggragation as CustomAggregation).aggregationFun ); this.fieldAggregators.push(aggregator); columnObjs[i].aggregator = aggregator; diff --git a/packages/vtable/src/dataset/statistics-helper.ts b/packages/vtable/src/dataset/statistics-helper.ts index 60c6ff3e2..2fe6ed45a 100644 --- a/packages/vtable/src/dataset/statistics-helper.ts +++ b/packages/vtable/src/dataset/statistics-helper.ts @@ -10,24 +10,12 @@ export abstract class Aggregator { field?: string | string[]; formatFun?: any; _formatedValue?: any; - needSplitPositiveAndNegativeForSum?: boolean = false; - constructor( - dimension: string | string[], - formatFun?: any, - isRecord?: boolean, - needSplitPositiveAndNegative?: boolean - ) { + + constructor(dimension: string, formatFun?: any, isRecord?: boolean) { this.field = dimension; - this.needSplitPositiveAndNegativeForSum = needSplitPositiveAndNegative ?? false; this.formatFun = formatFun; this.isRecord = isRecord ?? this.isRecord; } - // push(record: any) { - // if (this.isRecord) { - // if (record.className === 'Aggregator') this.records.push(...record.records); - // else this.records.push(record); - // } - // } abstract push(record: any): void; abstract value(): any; abstract recalculate(): any; @@ -93,12 +81,52 @@ export class NoneAggregator extends Aggregator { // do nothing } } +export class CustomAggregator extends Aggregator { + type: string = AggregationType.CUSTOM; //仅获取其中一条数据 不做聚合 其fieldValue可以是number或者string类型 + isRecord?: boolean = true; + declare field?: string; + aggregationFun: Function; + values: (string | number)[] = []; + fieldValue?: any; + constructor(dimension: string, formatFun?: any, isRecord?: boolean, aggregationFun?: Function) { + super(dimension, formatFun, isRecord); + this.aggregationFun = aggregationFun; + } + push(record: any): void { + if (this.isRecord) { + if (record.className === 'Aggregator') { + this.records.push(...record.records); + } else { + this.records.push(record); + } + } + this.values.push(record[this.field]); + } + value() { + if (!this.fieldValue) { + this.fieldValue = this.aggregationFun?.(this.values, this.records, this.field); + } + return this.fieldValue; + } + reset() { + this.records = []; + this.fieldValue = undefined; + } + recalculate() { + // do nothing + } +} export class SumAggregator extends Aggregator { type: string = AggregationType.SUM; sum = 0; positiveSum = 0; nagetiveSum = 0; declare field?: string; + needSplitPositiveAndNegativeForSum?: boolean = false; + constructor(dimension: string, formatFun?: any, isRecord?: boolean, needSplitPositiveAndNegative?: boolean) { + super(dimension, formatFun, isRecord); + this.needSplitPositiveAndNegativeForSum = needSplitPositiveAndNegative ?? false; + } push(record: any): void { if (this.isRecord) { if (record.className === 'Aggregator') { diff --git a/packages/vtable/src/ts-types/list-table/define/basic-define.ts b/packages/vtable/src/ts-types/list-table/define/basic-define.ts index 2f0fb70cb..b74ecf5cb 100644 --- a/packages/vtable/src/ts-types/list-table/define/basic-define.ts +++ b/packages/vtable/src/ts-types/list-table/define/basic-define.ts @@ -6,7 +6,7 @@ import type { ColumnIconOption } from '../../icon'; import type { MenuListItem } from '../../menu'; import type { BaseTableAPI } from '../../base-table'; import type { IEditor } from '@visactor/vtable-editors'; -import type { Aggregation, AggregationType } from '../../new-data-set'; +import type { Aggregation, CustomAggregation } from '../../new-data-set'; // eslint-disable-next-line no-unused-vars export interface IBasicHeaderDefine { @@ -83,5 +83,5 @@ export interface IBasicColumnBodyDefine { customRender?: ICustomRender; customLayout?: ICustomLayout; editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); - aggregation?: Aggregation | Aggregation[]; + aggregation?: Aggregation | CustomAggregation | (Aggregation | CustomAggregation)[]; } diff --git a/packages/vtable/src/ts-types/new-data-set.ts b/packages/vtable/src/ts-types/new-data-set.ts index cca1f5345..5d0a3be7e 100644 --- a/packages/vtable/src/ts-types/new-data-set.ts +++ b/packages/vtable/src/ts-types/new-data-set.ts @@ -16,7 +16,8 @@ export enum AggregationType { MIN = 'MIN', MAX = 'MAX', AVG = 'AVG', - COUNT = 'COUNT' + COUNT = 'COUNT', + CUSTOM = 'CUSTOM' } export enum SortType { ASC = 'ASC', @@ -231,7 +232,14 @@ export type CollectedValue = { max?: number; min?: number } | Array; //#region 提供给基本表格的类型 export type Aggregation = { - aggregationType?: AggregationType; + aggregationType: AggregationType; + showOnTop?: boolean; + formatFun?: (value: number, col: number, row: number, table: BaseTableAPI) => string | number; +}; + +export type CustomAggregation = { + aggregationType: AggregationType.CUSTOM; + aggregationFun: (values: any[], records: any[]) => any; showOnTop?: boolean; formatFun?: (value: number, col: number, row: number, table: BaseTableAPI) => string | number; }; diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index 2957ab529..6e2fe1695 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -4,7 +4,13 @@ export type { HeaderData } from './list-table/layout-map/api'; export type LayoutObjectId = number | string; import type { Rect } from '../tools/Rect'; import type { BaseTableAPI, BaseTableConstructorOptions, ListTableProtected } from './base-table'; -import type { Aggregation, AggregationType, FilterRules, IPivotTableDataConfig } from './new-data-set'; +import type { + Aggregation, + AggregationType, + CustomAggregation, + FilterRules, + IPivotTableDataConfig +} from './new-data-set'; import type { Either } from '../tools/helper'; import type { IChartIndicator, @@ -178,8 +184,12 @@ export interface ListTableConstructorOptions extends BaseTableConstructorOptions frozenColDragHeaderMode?: 'disabled' | 'adjustFrozenCount' | 'fixedFrozenCount'; aggregation?: | Aggregation - | Aggregation[] - | ((args: { col: number; field: string }) => Aggregation | Aggregation[] | null); + | CustomAggregation + | (Aggregation | CustomAggregation)[] + | ((args: { + col: number; + field: string; + }) => Aggregation | CustomAggregation | (Aggregation | CustomAggregation)[] | null); } export interface ListTableAPI extends BaseTableAPI { From faed452f1f6dc93fea2c36c2af67834c50721efb Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 8 Feb 2024 17:51:06 +0800 Subject: [PATCH 19/63] docs: update changlog of rush --- ...8-feature-listtable-caculate_2024-02-08-09-51.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-08-09-51.json diff --git a/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-08-09-51.json b/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-08-09-51.json new file mode 100644 index 000000000..40e03b3df --- /dev/null +++ b/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-08-09-51.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: add custom aggregation\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From c62fa3ec3ed32dbd1e5fd169fecc26d8f7d6e74c Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Sun, 18 Feb 2024 21:25:15 +0800 Subject: [PATCH 20/63] docs: add list table aggregation demo --- docs/assets/demo/menu.json | 34 ++- .../list-table-aggregation-multiple.md | 245 ++++++++++++++++++ .../list-table-aggregation.md | 235 +++++++++++++++++ .../list-table-data-filter.md | 235 +++++++++++++++++ .../option/en/column/base-column-type.md | 3 + .../option/zh/column/base-column-type.md | 3 + .../list-analysis/list-aggregation-edit.ts | 3 +- .../examples/list-analysis/olympic-winners.ts | 35 ++- 8 files changed, 776 insertions(+), 17 deletions(-) create mode 100644 docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md create mode 100644 docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md create mode 100644 docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md diff --git a/docs/assets/demo/menu.json b/docs/assets/demo/menu.json index bb449c76e..9881b2b98 100644 --- a/docs/assets/demo/menu.json +++ b/docs/assets/demo/menu.json @@ -869,8 +869,8 @@ { "path": "data-analysis", "title": { - "zh": "数据分析", - "en": "Data Analysis" + "zh": "透视表数据分析", + "en": "Pivot Table Data Analysis" }, "children": [ { @@ -931,6 +931,36 @@ } ] }, + { + "path": "list-table-data-analysis", + "title": { + "zh": "基本表数据分析", + "en": "List Table Data Analysis" + }, + "children": [ + { + "path": "list-table-data-filter", + "title": { + "zh": "数据过滤", + "en": "Data Filter" + } + }, + { + "path": "list-table-aggregation", + "title": { + "zh": "数据聚合分析", + "en": "Data Aggregation" + } + }, + { + "path": "list-table-aggregation-multiple", + "title": { + "zh": "多种数据聚合", + "en": "Multiple Data Aggregation" + } + } + ] + }, { "path": "component", "title": { diff --git a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md new file mode 100644 index 000000000..0f2799aa2 --- /dev/null +++ b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md @@ -0,0 +1,245 @@ +--- +category: examples +group: list-table-data-analysis +title: 同一列数据设置多种聚合汇总方式 +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-analysis-aggregation.png +link: '../guide/table_type/Pivot_table/pivot_table_dataAnalysis' +option: PivotTable#dataConfig.aggregationRules +--- + +# 同一列数据设置多种聚合汇总方式 + +基本表格聚合计算,每一列可以设置聚合方式,支持求和,平均,最大最小,自定义函数汇总逻辑。同一列和设置多种聚合方式,结果展示在多行。 + +且该示例数据支持编辑,编辑后自动计算需要聚合的值。 + +## 关键配置 + +- `ListTable` +- `columns.aggregation` 配置聚合计算 +## 代码演示 + +```javascript livedemo template=vtable + +const input_editor = new VTable_editors.InputEditor({}); +VTable.register.editor('input', input_editor); +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + salary: Math.round(Math.random() * 10000) + })); +}; +var tableInstance; + + const records = generatePersons(300); + const columns = [ + { + field: '', + title: '行号', + width: 80, + fieldFormat(data, col, row, table) { + return row - 1; + }, + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun() { + return '汇总:'; + } + } + }, + { + field: 'id', + title: 'ID', + width: '1%', + minWidth: 200, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'salary', + title: 'salary', + width: 100 + }, + { + field: 'salary', + title: 'salary', + width: 100, + aggregation: [ + { + aggregationType: VTable.TYPES.AggregationType.MAX, + // showOnTop: true, + formatFun(value) { + return '最高薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: VTable.TYPES.AggregationType.MIN, + showOnTop: true, + formatFun(value) { + return '最低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: VTable.TYPES.AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ] + } + ]; + const option = { + container: document.getElementById(CONTAINER_ID), + records, + // dataConfig: { + // filterRules: [ + // { + // filterFunc: (record: Record) => { + // return record.id % 2 === 0; + // } + // } + // ] + // }, + columns, + tooltip: { + isShowOverflowTextTooltip: true + }, + frozenColCount: 1, + bottomFrozenRowCount: 2, + rightFrozenColCount: 1, + overscrollBehavior: 'none', + autoWrapText: true, + widthMode: 'autoWidth', + heightMode: 'autoHeight', + dragHeaderMode: 'all', + keyboardOptions: { + pasteValueToCell: true + }, + eventOptions: { + preventDefaultContextMenu: false + }, + pagination: { + perPageCount: 100, + currentPage: 0 + }, + theme: VTable.themes.DEFAULT.extends({ + bottomFrozenStyle: { + bgColor: '#ECF1F5', + borderLineWidth: [6, 0, 1, 0], + borderColor: ['gray'] + } + }), + editor: 'input', + headerEditor: 'input', + aggregation(args) { + if (args.col === 1) { + return [ + { + aggregationType: VTable.TYPES.AggregationType.MAX, + formatFun(value) { + return '最大ID:' + Math.round(value) + '号'; + } + }, + { + aggregationType: VTable.TYPES.AggregationType.MIN, + showOnTop: false, + formatFun(value, col, row, table) { + return '最小ID:' + Math.round(value) + '号'; + } + } + ]; + } + if (args.field === 'salary') { + return [ + { + aggregationType: VTable.TYPES.AggregationType.MIN, + formatFun(value) { + return '最低低低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: VTable.TYPES.AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均平均平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ]; + } + return null; + } + // transpose: true + // widthMode: 'adaptive' + }; + tableInstance = new VTable.ListTable(option); + // tableInstance.updateFilterRules([ + // { + // filterKey: 'sex', + // filteredValues: ['boy'] + // } + // ]); + window.tableInstance = tableInstance; + tableInstance.on('change_cell_value', arg => { + console.log(arg); + }); + +``` diff --git a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md new file mode 100644 index 000000000..cb7672a13 --- /dev/null +++ b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md @@ -0,0 +1,235 @@ +--- +category: examples +group: list-table-data-analysis +title: 基本表格数据聚合分析 +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-analysis-aggregation.png +link: '../guide/table_type/Pivot_table/pivot_table_dataAnalysis' +option: PivotTable#dataConfig.aggregationRules +--- + +# 数据聚合汇总 + +基本表格聚合计算,每一列可以设置聚合方式,支持求和,平均,最大最小,自定义函数汇总逻辑。 + +## 关键配置 + +- `ListTable` +- `columns.aggregation` 配置聚合计算 +## 代码演示 + +```javascript livedemo template=vtable +var tableInstance; +VTable.register.icon('filter', { + name: 'filter', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' + }); + + VTable.register.icon('filtered', { + name: 'filtered', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' + }); + fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') + .then(res => res.json()) + .then(data => { + const columns = [ + { + field: 'athlete', + title: 'athlete', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun(value) { + return 'Total:'; + } + } + }, + { + field: 'age', + title: 'age', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.AVG, + formatFun(value) { + return Math.round(value) + '(Avg)'; + } + } + }, + { + field: 'country', + title: 'country', + headerIcon: 'filter', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.CUSTOM, + aggregationFun(values, records) { + // 使用 reduce() 方法统计金牌数 + const goldMedalCountByCountry = records.reduce((acc, data) => { + const country = data.country; + const gold = data.gold; + + if (acc[country]) { + acc[country] += gold; + } else { + acc[country] = gold; + } + return acc; + }, {}); + + // 找出金牌数最多的国家 + let maxGoldMedals = 0; + let countryWithMaxGoldMedals = ''; + + for (const country in goldMedalCountByCountry) { + if (goldMedalCountByCountry[country] > maxGoldMedals) { + maxGoldMedals = goldMedalCountByCountry[country]; + countryWithMaxGoldMedals = country; + } + } + return { + country: countryWithMaxGoldMedals, + gold: maxGoldMedals + }; + }, + formatFun(value) { + return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; + } + } + }, + { field: 'year', title: 'year', headerIcon: 'filter' }, + { field: 'sport', title: 'sport', headerIcon: 'filter' }, + { + field: 'gold', + title: 'gold', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'silver', + title: 'silver', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'bronze', + title: 'bronze', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'total', + title: 'total', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + } + ]; + const option = { + columns, + records: data, + autoWrapText: true, + heightMode: 'autoHeight', + widthMode: 'autoWidth', + bottomFrozenRowCount: 1, + theme:VTable.themes.ARCO.extends({ + bottomFrozenStyle:{ + fontFamily: 'PingFang SC', + fontWeight: 500, + } + }) + }; + const t0 = window.performance.now(); + tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + window.tableInstance = tableInstance; + const filterListValues = { + country: ['all', 'China', 'United States', 'Australia'], + year: ['all', '2004', '2008', '2012', '2016', '2020'], + sport: ['all', 'Swimming', 'Cycling', 'Biathlon', 'Short-Track Speed Skating', 'Nordic Combined'] + }; + let filterListSelectedValues = ''; + let lastFilterField; + tableInstance.on('icon_click', args => { + const { col, row, name } = args; + if (name === 'filter') { + const field = tableInstance.getHeaderField(col, row); + if (select && lastFilterField === field) { + removeFilterElement(); + lastFilterField = null; + } else if (!select || lastFilterField !== field) { + const rect = tableInstance.getCellRelativeRect(col, row); + createFilterElement(filterListValues[field], filterListSelectedValues, field, rect); + lastFilterField = field; + } + } + }); + + let filterContainer = tableInstance.getElement(); + let select; + function createFilterElement(values, curValue, field, positonRect) { + // create select tag + select = document.createElement('select'); + select.setAttribute('type', 'text'); + select.style.position = 'absolute'; + select.style.padding = '4px'; + select.style.width = '100%'; + select.style.boxSizing = 'border-box'; + + // create option tags + let opsStr = ''; + values.forEach(item => { + opsStr += + item === curValue + ? `` + : ``; + }); + select.innerHTML = opsStr; + + filterContainer.appendChild(select); + + select.style.top = positonRect.top + positonRect.height + 'px'; + select.style.left = positonRect.left + 'px'; + select.style.width = positonRect.width + 'px'; + select.style.height = positonRect.height + 'px'; + select.addEventListener('change', () => { + filterListSelectedValues = select.value; + tableInstance.updateFilterRules([ + { + filterKey: field, + filteredValues: select.value + } + ]); + removeFilterElement(); + }); + } + function removeFilterElement() { + filterContainer.removeChild(select); + select.removeEventListener('change', () => { + // this.successCallback(); + }); + select = null; + } + }); +``` diff --git a/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md b/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md new file mode 100644 index 000000000..51fe54da0 --- /dev/null +++ b/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md @@ -0,0 +1,235 @@ +--- +category: examples +group: list-table-data-analysis +title: 基本表格数据过滤 +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-analysis-aggregation.png +link: '../guide/table_type/Pivot_table/pivot_table_dataAnalysis' +option: PivotTable#dataConfig.aggregationRules +--- + +# 基本表格数据过滤 + +基本表格通过接口updateFilterRules来设置过滤,支持值过滤和函数过滤。 + +## 关键配置 + +- `ListTable` +- `updateFilterRules` 设置或者更新过滤数据规则 +## 代码演示 + +```javascript livedemo template=vtable +var tableInstance; +VTable.register.icon('filter', { + name: 'filter', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' + }); + + VTable.register.icon('filtered', { + name: 'filtered', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' + }); + fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') + .then(res => res.json()) + .then(data => { + const columns = [ + { + field: 'athlete', + title: 'athlete', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun(value) { + return 'Total:'; + } + } + }, + { + field: 'age', + title: 'age', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.AVG, + formatFun(value) { + return Math.round(value) + '(Avg)'; + } + } + }, + { + field: 'country', + title: 'country', + headerIcon: 'filter', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.CUSTOM, + aggregationFun(values, records) { + // 使用 reduce() 方法统计金牌数 + const goldMedalCountByCountry = records.reduce((acc, data) => { + const country = data.country; + const gold = data.gold; + + if (acc[country]) { + acc[country] += gold; + } else { + acc[country] = gold; + } + return acc; + }, {}); + + // 找出金牌数最多的国家 + let maxGoldMedals = 0; + let countryWithMaxGoldMedals = ''; + + for (const country in goldMedalCountByCountry) { + if (goldMedalCountByCountry[country] > maxGoldMedals) { + maxGoldMedals = goldMedalCountByCountry[country]; + countryWithMaxGoldMedals = country; + } + } + return { + country: countryWithMaxGoldMedals, + gold: maxGoldMedals + }; + }, + formatFun(value) { + return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; + } + } + }, + { field: 'year', title: 'year', headerIcon: 'filter' }, + { field: 'sport', title: 'sport', headerIcon: 'filter' }, + { + field: 'gold', + title: 'gold', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'silver', + title: 'silver', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'bronze', + title: 'bronze', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'total', + title: 'total', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + } + ]; + const option = { + columns, + records: data, + autoWrapText: true, + heightMode: 'autoHeight', + widthMode: 'autoWidth', + bottomFrozenRowCount: 1, + theme:VTable.themes.ARCO.extends({ + bottomFrozenStyle:{ + fontFamily: 'PingFang SC', + fontWeight: 500, + } + }) + }; + const t0 = window.performance.now(); + tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + window.tableInstance = tableInstance; + const filterListValues = { + country: ['all', 'China', 'United States', 'Australia'], + year: ['all', '2004', '2008', '2012', '2016', '2020'], + sport: ['all', 'Swimming', 'Cycling', 'Biathlon', 'Short-Track Speed Skating', 'Nordic Combined'] + }; + let filterListSelectedValues = ''; + let lastFilterField; + tableInstance.on('icon_click', args => { + const { col, row, name } = args; + if (name === 'filter') { + const field = tableInstance.getHeaderField(col, row); + if (select && lastFilterField === field) { + removeFilterElement(); + lastFilterField = null; + } else if (!select || lastFilterField !== field) { + const rect = tableInstance.getCellRelativeRect(col, row); + createFilterElement(filterListValues[field], filterListSelectedValues, field, rect); + lastFilterField = field; + } + } + }); + + let filterContainer = tableInstance.getElement(); + let select; + function createFilterElement(values, curValue, field, positonRect) { + // create select tag + select = document.createElement('select'); + select.setAttribute('type', 'text'); + select.style.position = 'absolute'; + select.style.padding = '4px'; + select.style.width = '100%'; + select.style.boxSizing = 'border-box'; + + // create option tags + let opsStr = ''; + values.forEach(item => { + opsStr += + item === curValue + ? `` + : ``; + }); + select.innerHTML = opsStr; + + filterContainer.appendChild(select); + + select.style.top = positonRect.top + positonRect.height + 'px'; + select.style.left = positonRect.left + 'px'; + select.style.width = positonRect.width + 'px'; + select.style.height = positonRect.height + 'px'; + select.addEventListener('change', () => { + filterListSelectedValues = select.value; + tableInstance.updateFilterRules([ + { + filterKey: field, + filteredValues: select.value + } + ]); + removeFilterElement(); + }); + } + function removeFilterElement() { + filterContainer.removeChild(select); + select.removeEventListener('change', () => { + // this.successCallback(); + }); + select = null; + } + }); +``` diff --git a/docs/assets/option/en/column/base-column-type.md b/docs/assets/option/en/column/base-column-type.md index ceeacb5e1..2c6300d4a 100644 --- a/docs/assets/option/en/column/base-column-type.md +++ b/docs/assets/option/en/column/base-column-type.md @@ -219,3 +219,6 @@ headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI } ${prefix} columns (Array) Configure arrays with upper columns, nesting structures to describe column grouping relationships. + +${prefix} hideColumnsSubHeader(boolean) = false +Whether to hide the header title of the subtable header. The default value is not hidden. \ No newline at end of file diff --git a/docs/assets/option/zh/column/base-column-type.md b/docs/assets/option/zh/column/base-column-type.md index 5aaeac17d..fb7054389 100644 --- a/docs/assets/option/zh/column/base-column-type.md +++ b/docs/assets/option/zh/column/base-column-type.md @@ -228,3 +228,6 @@ headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI } ${prefix} columns (Array) 同上层的列配置数组,嵌套结构来描述列分组关系。 + +${prefix} hideColumnsSubHeader(boolean) = false +是否隐藏子表头的header标题,默认不隐藏。 \ No newline at end of file diff --git a/packages/vtable/examples/list-analysis/list-aggregation-edit.ts b/packages/vtable/examples/list-analysis/list-aggregation-edit.ts index 694ffa2f2..26ec14992 100644 --- a/packages/vtable/examples/list-analysis/list-aggregation-edit.ts +++ b/packages/vtable/examples/list-analysis/list-aggregation-edit.ts @@ -113,6 +113,7 @@ export function createTable() { }, { aggregationType: AggregationType.MIN, + showOnTop: true, formatFun(value) { return '最低薪资:' + Math.round(value) + '元'; } @@ -144,7 +145,7 @@ export function createTable() { isShowOverflowTextTooltip: true }, frozenColCount: 1, - bottomFrozenRowCount: 3, + bottomFrozenRowCount: 2, rightFrozenColCount: 1, overscrollBehavior: 'none', autoWrapText: true, diff --git a/packages/vtable/examples/list-analysis/olympic-winners.ts b/packages/vtable/examples/list-analysis/olympic-winners.ts index 383f857fa..468c98eb7 100644 --- a/packages/vtable/examples/list-analysis/olympic-winners.ts +++ b/packages/vtable/examples/list-analysis/olympic-winners.ts @@ -3,6 +3,7 @@ import * as VTable from '../../src'; const CONTAINER_ID = 'vTable'; export function createTable() { + var tableInstance; VTable.register.icon('filter', { name: 'filter', type: 'svg', @@ -27,7 +28,7 @@ export function createTable() { fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') .then(res => res.json()) .then(data => { - const columns: VTable.ColumnDefine[] = [ + const columns = [ { field: 'athlete', title: 'athlete', @@ -54,7 +55,7 @@ export function createTable() { headerIcon: 'filter', aggregation: { aggregationType: VTable.TYPES.AggregationType.CUSTOM, - aggregationFun(values, records: any[]) { + aggregationFun(values, records) { // 使用 reduce() 方法统计金牌数 const goldMedalCountByCountry = records.reduce((acc, data) => { const country = data.country; @@ -83,7 +84,7 @@ export function createTable() { gold: maxGoldMedals }; }, - formatFun(value: any) { + formatFun(value) { return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; } } @@ -131,17 +132,23 @@ export function createTable() { } } ]; - const option: VTable.ListTableConstructorOptions = { + const option = { columns, records: data, autoWrapText: true, heightMode: 'autoHeight', widthMode: 'autoWidth', - bottomFrozenRowCount: 1 + bottomFrozenRowCount: 1, + theme: VTable.themes.ARCO.extends({ + bottomFrozenStyle: { + fontFamily: 'PingFang SC', + fontWeight: 500 + } + }) }; const t0 = window.performance.now(); - const instance = new VTable.ListTable(document.getElementById(CONTAINER_ID)!, option); - window.tableInstance = instance; + tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + window.tableInstance = tableInstance; const filterListValues = { country: ['all', 'China', 'United States', 'Australia'], year: ['all', '2004', '2008', '2012', '2016', '2020'], @@ -149,24 +156,24 @@ export function createTable() { }; let filterListSelectedValues = ''; let lastFilterField; - instance.on('icon_click', args => { + tableInstance.on('icon_click', args => { const { col, row, name } = args; if (name === 'filter') { - const field = instance.getHeaderField(col, row); + const field = tableInstance.getHeaderField(col, row); if (select && lastFilterField === field) { removeFilterElement(); lastFilterField = null; } else if (!select || lastFilterField !== field) { - const rect = instance.getCellRelativeRect(col, row); - createFilterElement(filterListValues[field as string], filterListSelectedValues, field, rect); + const rect = tableInstance.getCellRelativeRect(col, row); + createFilterElement(filterListValues[field], filterListSelectedValues, field, rect); lastFilterField = field; } } }); - let filterContainer = instance.getElement(); + let filterContainer = tableInstance.getElement(); let select; - function createFilterElement(values: string[], curValue: string, field, positonRect) { + function createFilterElement(values, curValue, field, positonRect) { // create select tag select = document.createElement('select'); select.setAttribute('type', 'text'); @@ -193,7 +200,7 @@ export function createTable() { select.style.height = positonRect.height + 'px'; select.addEventListener('change', () => { filterListSelectedValues = select.value; - instance.updateFilterRules([ + tableInstance.updateFilterRules([ { filterKey: field, filteredValues: select.value From b3f44b354257f4dbec931ec87dee5edab5ef6d2d Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Sun, 18 Feb 2024 21:34:39 +0800 Subject: [PATCH 21/63] docs: add list table aggregation demo --- packages/vtable/src/dataset/dataset-pivot-table.ts | 3 +++ packages/vtable/src/dataset/dataset.ts | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/vtable/src/dataset/dataset-pivot-table.ts b/packages/vtable/src/dataset/dataset-pivot-table.ts index 5ba629cb0..49945decf 100644 --- a/packages/vtable/src/dataset/dataset-pivot-table.ts +++ b/packages/vtable/src/dataset/dataset-pivot-table.ts @@ -415,6 +415,9 @@ export class DatasetForPivotTable { push() { // do nothing }, + recalculate() { + // do nothing + }, value(): any { return null; }, diff --git a/packages/vtable/src/dataset/dataset.ts b/packages/vtable/src/dataset/dataset.ts index 2873de2f0..016ce82c1 100644 --- a/packages/vtable/src/dataset/dataset.ts +++ b/packages/vtable/src/dataset/dataset.ts @@ -857,6 +857,9 @@ export class Dataset { formatFun: agg.formatFun, records: agg.records, className: '', + recalculate() { + // do nothing + }, push() { // do nothing }, @@ -880,6 +883,9 @@ export class Dataset { push() { // do nothing }, + recalculate() { + // do nothing + }, formatValue() { return changeValue; }, @@ -900,6 +906,9 @@ export class Dataset { push() { // do nothing }, + recalculate() { + // do nothing + }, value(): any { return null; }, From b61bb9c46e907a6ccd3d8e76c70a2340a5167c0e Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 19 Feb 2024 15:34:15 +0800 Subject: [PATCH 22/63] refactor: aggregation value should not trigger edit --- .../vtable/examples/list-analysis/list-aggregation-edit.ts | 2 +- packages/vtable/src/dataset/statistics-helper.ts | 1 - packages/vtable/src/edit/edit-manager.ts | 5 +++++ packages/vtable/src/layout/row-height-map.ts | 1 + packages/vtable/src/scenegraph/scenegraph.ts | 5 ----- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/vtable/examples/list-analysis/list-aggregation-edit.ts b/packages/vtable/examples/list-analysis/list-aggregation-edit.ts index 26ec14992..04e832633 100644 --- a/packages/vtable/examples/list-analysis/list-aggregation-edit.ts +++ b/packages/vtable/examples/list-analysis/list-aggregation-edit.ts @@ -27,7 +27,7 @@ export function createTable() { title: '行号', width: 80, fieldFormat(data, col, row, table) { - return row - 1; + return row - 2; }, aggregation: { aggregationType: AggregationType.NONE, diff --git a/packages/vtable/src/dataset/statistics-helper.ts b/packages/vtable/src/dataset/statistics-helper.ts index 2fe6ed45a..1a718aed8 100644 --- a/packages/vtable/src/dataset/statistics-helper.ts +++ b/packages/vtable/src/dataset/statistics-helper.ts @@ -332,7 +332,6 @@ export class MaxAggregator extends Aggregator { export class MinAggregator extends Aggregator { type: string = AggregationType.MIN; min: number = Number.MAX_SAFE_INTEGER; - isRecord?: boolean = false; declare field?: string; push(record: any): void { if (this.isRecord) { diff --git a/packages/vtable/src/edit/edit-manager.ts b/packages/vtable/src/edit/edit-manager.ts index 81ba891b8..1a7a5462f 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -3,6 +3,7 @@ import { TABLE_EVENT_TYPE } from '../core/TABLE_EVENT_TYPE'; import type { BaseTableAPI } from '../ts-types/base-table'; import type { ListTableAPI, ListTableConstructorOptions } from '../ts-types'; import { getCellEventArgsSet } from '../event/util'; +import type { SimpleHeaderLayoutMap } from '../layout'; export class EditManeger { table: BaseTableAPI; @@ -71,6 +72,10 @@ export class EditManeger { return; } } + if ((this.table.internalProps.layoutMap as SimpleHeaderLayoutMap)?.isAggregation?.(col, row)) { + console.warn("VTable Warn: this is aggregation value, can't be edited"); + return; + } this.editingEditor = editor; this.editCell = { col, row }; const dataValue = this.table.getCellOriginValue(col, row); diff --git a/packages/vtable/src/layout/row-height-map.ts b/packages/vtable/src/layout/row-height-map.ts index 130c80a71..bcb504eb0 100644 --- a/packages/vtable/src/layout/row-height-map.ts +++ b/packages/vtable/src/layout/row-height-map.ts @@ -25,6 +25,7 @@ export class NumberRangeMap { } clear() { + this._keys = []; this.data.clear(); this.cumulativeSum.clear(); this.difference.clear(); diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index 1f1117ac1..019c1ea83 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -1427,12 +1427,7 @@ export class Scenegraph { this.updateTableSize(); - // 记录滚动条原位置 - const oldHorizontalBarPos = this.table.stateManager.scroll.horizontalBarPos; - const oldVerticalBarPos = this.table.stateManager.scroll.verticalBarPos; this.component.updateScrollBar(); - this.table.stateManager.setScrollLeft(oldHorizontalBarPos); - this.table.stateManager.setScrollTop(oldVerticalBarPos); this.updateNextFrame(); } From 80d1dbed299ed96b0618bd4d8d76c4c9c0ed91a7 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 19 Feb 2024 18:07:14 +0800 Subject: [PATCH 23/63] docs: add list table aggregation tutorial --- .../data_analysis/list_table_dataAnalysis.md | 141 +++++++ .../data_analysis/pivot_table_dataAnalysis.md | 377 ++++++++++++++++++ docs/assets/guide/menu.json | 23 ++ .../data_analysis/list_table_dataAnalysis.md | 141 +++++++ .../data_analysis/pivot_table_dataAnalysis.md | 339 ++++++++++++++++ packages/vtable/src/PivotTable.ts | 10 + .../vtable/src/layout/pivot-header-layout.ts | 14 +- 7 files changed, 1044 insertions(+), 1 deletion(-) create mode 100644 docs/assets/guide/en/data_analysis/list_table_dataAnalysis.md create mode 100644 docs/assets/guide/en/data_analysis/pivot_table_dataAnalysis.md create mode 100644 docs/assets/guide/zh/data_analysis/list_table_dataAnalysis.md create mode 100644 docs/assets/guide/zh/data_analysis/pivot_table_dataAnalysis.md diff --git a/docs/assets/guide/en/data_analysis/list_table_dataAnalysis.md b/docs/assets/guide/en/data_analysis/list_table_dataAnalysis.md new file mode 100644 index 000000000..2c26a0250 --- /dev/null +++ b/docs/assets/guide/en/data_analysis/list_table_dataAnalysis.md @@ -0,0 +1,141 @@ +# Basic table data analysis + +Currently supported capabilities include sorting, filtering, and data aggregation calculations. + +# Data sorting + +For details, please refer to the tutorial: https://visactor.io/vtable/guide/basic_function/sort + +# Data filtering + +The basic table component sets data filtering rules through the interface `updateFilterRules`, supporting value filtering and function filtering. Here is a usage example of filtering data: + +```javascript +tableInstance.updateFilterRules([ + { + filterKey: 'sex', + filteredValues: ['boy'] + }, + { + filterFunc: (record: Record) => { + return record.age > 30; + } + } +]); +``` + +In the above example, we set up value filtering through `filterKey` and `filteredValues` to only display data with a gender of "boy"; at the same time, we used function filtering to customize the filtering logic through `filterFunc` and only displayed `age The `field is the data whose age is greater than 30. + +Specific example: https://visactor.io/vtable/demo/list-table-data-analysis/list-table-data-filter + +# Data aggregation + +The basic table supports aggregation calculation of data, and different aggregation methods can be set for each column, including sum, average, maximum value, minimum value, and custom function summary logic. Multiple aggregation methods can be set for the same column, and the aggregation results will be displayed in multiple rows. + +## Aggregation calculation type + +- To sum, set `aggregationType` to `AggregationType.SUM` +- Average, set `aggregationType` to `AggregationType.AVG` +- Maximum value, set `aggregationType` to `AggregationType.MAX` +- Minimum value, set `aggregationType` to `AggregationType.MIN` +- Count, set `aggregationType` to `AggregationType.COUNT` +- Custom function, set `aggregationType` to `AggregationType.CUSTOM`, and set custom aggregation logic through `aggregationFun` + +## Aggregate value formatting function + +Use `formatFun` to set the formatting function of the aggregate value, and you can customize the display format of the aggregate value. + +## Aggregated result placement + +Use `showOnTop` to control the display position of the aggregation results. The default is `false`, that is, the aggregation results are displayed at the bottom of the body. If set to `true`, the aggregation results are displayed at the top of the body. + +Note: Currently, the aggregate value does not have the ability to customize freezing. It needs to be combined with bottomFrozonRowCount to achieve fixed display. In addition, the embarrassing thing is that topFrozonRowCount has not been added yet, so it is recommended to display the aggregation result at the bottom of the body first. Comprehensive freezing capabilities will be supported in the future. + +## Aggregation configuration + +Aggregation configuration can be set in the `columns` column definition or configured in the table global `option`. + +### Configure aggregation method in column definition + +In the column definition, the aggregation method can be configured through the `aggregation` attribute. Here is an example of an aggregation configuration: + +```javascript +columns: { + field: 'salary', + title: 'salary', + width: 100, + aggregation: [ + { + aggregationType: AggregationType.MAX, + formatFun(value) { + return 'Maximum salary:' + Math.round(value) + 'yuan'; + } + }, + { + aggregationType: AggregationType.MIN, + formatFun(value) { + return 'Minimum salary:' + Math.round(value) + 'yuan'; + } + }, + { + aggregationType: AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return 'Average:' + Math.round(value) + 'Yuan (total' + table.recordsCount + 'data)'; + } + } + ] +} +``` + +In the above example, we set three aggregation methods for the `salary` column: maximum value, minimum value and average value. Use `aggregationType` to specify the aggregation method, and then use `formatFun` to customize the display format of the aggregation results, and use `showOnTop` to control whether the aggregation results are displayed at the top or bottom of the body. + +### Table global configuration aggregation method + +In addition to configuring the aggregation method in the column definition, you can also set it in the table global configuration. Here is an example of global configuration: + +```javascript +aggregation(args) { + if (args.col === 1) { + return [ + { + aggregationType: AggregationType.MAX, + formatFun(value) { + return 'Maximum ID:' + Math.round(value) + 'number'; + } + }, + { + aggregationType: AggregationType.MIN, + showOnTop: false, + formatFun(value, col, row, table) { + return 'Minimum ID:' + Math.round(value) + 'number'; + } + } + ]; + } + if (args.field === 'salary') { + return [ + { + aggregationType: AggregationType.MIN, + formatFun(value) { + return 'Minimum salary:' + Math.round(value) + 'yuan'; + } + }, + { + aggregationType: AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return 'Average salary:' + Math.round(value) + 'Yuan (total' + table.recordsCount + 'data)'; + } + } + ]; + } + return null; +} +``` + +In the above example, we set the aggregation method through the `aggregation` function of global configuration, and return different aggregation configurations according to different conditions. For example, when `args.col === 1`, we set the aggregation method of the maximum and minimum values; when `args.field === 'salary'`, we set the aggregation of the minimum and average values Way. + +For specific examples, please refer to: https://visactor.io/vtable/demo/list-table-data-analysis/list-table-aggregation-multiple + +The above is the tutorial document for basic table data analysis capabilities, covering the configuration and usage of data filtering and data aggregation. Hope this document can be helpful to you! If you have any questions, please feel free to ask. diff --git a/docs/assets/guide/en/data_analysis/pivot_table_dataAnalysis.md b/docs/assets/guide/en/data_analysis/pivot_table_dataAnalysis.md new file mode 100644 index 000000000..87e6d3bc5 --- /dev/null +++ b/docs/assets/guide/en/data_analysis/pivot_table_dataAnalysis.md @@ -0,0 +1,377 @@ +# Pivot data analysis + +In the figure below, there are four business dimensions: region, province, year, quarter, and indicators: sales, profit. + +
+ +

Pivot table structure description

+
+Regarding the sales data in the figure, the location is in cell [5, 5], that is, the data in column 5 and row 5: represents the sales profit value of Heilongjiang Province in the Northeast region in the Q2 quarter of 2016. That is to say, it corresponds to the row dimension value: ['Northeast', 'Heilongjiang'], the column dimension: ['2016', '2016-Q2'], and the indicator: 'Profit'. Next, we will introduce how to use VTable to implement this multi-dimensional table. + +# VTable implements multi-dimensional tables + +## Concept mapping to configuration items + +The configuration of the pivot table above is as follows: + +``` +const option={ + rows:['region','province'], //row dimensions + columns:['year','quarter'], //column dimensions + indicators:['sales','profit'], //Indicators + enableDataAnalysis: true, //Whether to enable data analysis function + records:[ //Data source。 If summary data is passed in, use user incoming data + { + region:'东北', + province:'黑龙江', + year:'2016', + quarter:'2016-Q1', + sales:1243, + profit:546 + }, + ... + ] +} +``` + +This configuration is the simplest configuration for multidimensional tables. As the functional requirements become more complex, various configurations can be added for each function point to meet the needs. + +## Data analysis related configuration: + +| Configuration item | Type | Description | +| :--------------------------- | :----------------------------- | :---------------------------------------------------------------------------------- | +| rows | (IRowDimension \| string)[] | Row dimension field array, used to parse out the corresponding dimension members | +| columns | (IColumnDimension \| string)[] | Column dimension field array, used to parse out the corresponding dimension members | +| indicators | (IIndicator \| string)[] | Specific display indicators | +| dataConfig.aggregationRules | aggregationRule[] | Aggregation value calculation rules according to row and column dimensions | +| dataConfig.derivedFieldRules | DerivedFieldRule[] | Derived fields | +| dataConfig.sortRules | sortRule[] | Sort rules | +| dataConfig.filterRules | filterRule[] | Filter Rules | +| dataConfig.totals | totalRule[] | Subtotal or total | + +dataConfig configuration definition: + +``` +/** + * Data processing configuration + */ +export interface IDataConfig { + aggregationRules?: AggregationRules; //按照行列维度聚合值计算规则; + sortRules?: SortRules; //排序规则; + filterRules?: FilterRules; //过滤规则; + totals?: Totals; //小计或总计; + derivedFieldRules?: DerivedFieldRules; //派生字段定义 + ... +} +``` + +dataConfig application example: + +### 1. Totals + +[option description](../../../option/PivotTable#dataConfig.totals) +Configuration example: + +``` +dataConfig: { + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['province'], + grandTotalLabel: 'row total', + subTotalLabel: 'Subtotal', + showGrandTotalsOnTop: true //totals show on top + }, + column: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['quarter'], + grandTotalLabel: 'column total', + subTotalLabel: 'Subtotal' + } + } + }, +``` + +Online demo:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-total + +### 2. Sorting rules + +[option description](../../../option/PivotTable#dataConfig.sortRules) +Configuration example: + +``` + sortRules: [ + { + sortField: 'city', + sortByIndicator: 'sales', + sortType: VTable.TYPES.SortType.DESC, + query: ['office supplies', 'pen'] + } as VTable.TYPES.SortByIndicatorRule + ] + +``` + +If you need to modify the sorting rules of the pivot table, you can use the interface `updateSortRules`. + +Online demo:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-sort-dimension + +### 3. Filter rules + +[option description](../../../option/PivotTable#dataConfig.filterRules) +Configuration example: + +``` +filterRules: [ + { + filterFunc: (record: Record) => { + return record.province !== 'Sichuan Province' || record.category !== 'Furniture'; + } + } + ] +``` + +Online demo:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-filter + +### 4. Aggregation method + +[option description](../../../option/PivotTable#dataConfig.aggregationRules) +Configuration example: + +``` + aggregationRules: [ + //The basis for doing aggregate calculations, such as sales. If there is no configuration, the cell content will be displayed by default based on the aggregate sum calculation result. + { + indicatorKey: 'TotalSales', //Indicator name + field: 'Sales', //Indicator based on field + aggregationType: VTable.TYPES.AggregationType.SUM, //Calculation type + formatFun: sumNumberFormat + }, + { + indicatorKey: 'OrderCount', //Indicator name + field: 'Sales', //Indicator based on field + aggregationType: VTable.TYPES.AggregationType.COUNT, //Computation type + formatFun: countNumberFormat + }, + { + indicatorKey: 'AverageOrderSales', //Indicator name + field: 'Sales', //Indicator based on field + aggregationType: VTable.TYPES.AggregationType.AVG, //Computation type + }, + { + indicatorKey: 'MaxOrderSales', //Indicator name + field: 'Sales', //Indicator based on field + aggregationType: VTable.TYPES.AggregationType.MAX, //Computation type , caculate max value + }, + { + indicatorKey: 'OrderSalesValue', //Indicator name + field: 'Sales', //Indicator based on field + aggregationType: VTable.TYPES.AggregationType.NONE, //don't aggregate + } + ] +``` + +Online demo:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-aggregation + +### 5. Derive Field + +[option description](../../../option/PivotTable#dataConfig.derivedFieldRules) +Configuration example: + +``` + derivedFieldRules: [ + { + fieldName: 'Year', + derivedFunc: VTable.DataStatistics.dateFormat('Order Date', '%y', true), + }, + { + fieldName: 'Month', + derivedFunc: VTable.DataStatistics.dateFormat('Order Date', '%n', true), + } + ] +``` + +Online demo:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-derivedField + +## Data analysis process + +Dependent configuration: dimensions, indicators and dataConfig. + +### The process of traversing data: + +Traverse the records once, parse the row header dimension value to display the header cell, distribute all data in the records to the corresponding row and column path set, and calculate the aggregate value of the body part indicator cell. + +
+ +

Data analysis process

+
+ +### Data dimension tree + +According to the above traversed structure, a dimension tree will be generated, from which the value of the cell and the original data entry of the value can be found. + +
+ +

Organize dimension tree to aggregate data

+
+ After analysis and calculation of record grouping and aggregation, the corresponding relationship between the cell data in the table and the records data source is finally presented: +
+ +

Correspondence between data source entries and cells

+
+ +### Custom dimension tree + +Although multi-dimensional tables with analytical capabilities can automatically analyze the dimension values of each dimension to form a tree structure of row and column headers, and can be sorted according to `dataConfig.sortRules`, scenarios with complex business logic still expect to be able to **customize Row column header dimension value ** and order. Then these business requirement scenarios can be realized through rowTree and columnTree. + +- enableDataAnalysis needs to be set to false to turn off the analysis of aggregated data within VTable. + +
+ +

custom rowTree columnTree

+
+ +Custom tree configuration: + +``` +const option = { + rowTree: [{ + dimensionKey: 'region', + value: '中南', + children: [ + { + dimensionKey: 'province', + value: '广东', + }, + { + dimensionKey: 'province', + value: '广西', + } + ] + }, + { + dimensionKey: 'region', + value: '华东', + children: [ + { + dimensionKey: 'province', + value: '上海', + }, + { + dimensionKey: 'province', + value: '山东', + } + ] + }], + columnTree: [{ + dimensionKey: 'year', + value: '2016', + children: [ + { + dimensionKey: 'quarter', + value: '2016-Q1', + children: [ + { + indicatorKey: 'sales', + value: 'sales' + }, + { + indicatorKey: 'profit', + value: 'profit' + } + ] + }, + { + dimensionKey: 'quarter', + value: '2016-Q2', + children: [ + { + indicatorKey: 'sales', + value: 'sales' + }, + { + indicatorKey: 'profit', + value: 'profit' + } + ] + } + ] + }], + indicators: ['sales', 'profit'], + //enableDataAnalysis:true, + corner: { + titleOnDimension: 'none' + }, + records: [ + { + region: '中南', + province: '广东', + year: '2016', + quarter: '2016-Q1', + sales: 1243, + profit: 546 + }, + { + region: '中南', + province: '广东', + year: '2016', + quarter: '2016-Q2', + sales: 2243, + profit: 169 + }, { + region: '中南', + province: '广西', + year: '2016', + quarter: '2016-Q1', + sales: 3043, + profit: 1546 + }, + { + region: '中南', + province: '广西', + year: '2016', + quarter: '2016-Q2', + sales: 1463, + profit: 609 + }, + { + region: '华东', + province: '上海', + year: '2016', + quarter: '2016-Q1', + sales: 4003, + profit: 1045 + }, + { + region: '华东', + province: '上海', + year: '2016', + quarter: '2016-Q2', + sales: 5243, + profit: 3169 + }, { + region: '华东', + province: '山东', + year: '2016', + quarter: '2016-Q1', + sales: 4543, + profit: 3456 + }, + { + region: '华东', + province: '山东', + year: '2016', + quarter: '2016-Q2', + sales: 6563, + profit: 3409 + } + ] +}; +``` + +VTable official website example: https://visactor.io/vtable/demo/table-type/pivot-table. + +The complexity of the custom tree lies in the formation of the row, column and dimension trees. You can choose to use it according to the business scenario. If you have complex sorting, aggregation or paging rules, you can choose to use a custom method. + +**Note: If you choose the custom tree configuration method, the data aggregation capability inside the VTable will not be enabled, that is, one of the matched data entries will be used as the cell indicator value. ** diff --git a/docs/assets/guide/menu.json b/docs/assets/guide/menu.json index b323ed93e..dc482abde 100644 --- a/docs/assets/guide/menu.json +++ b/docs/assets/guide/menu.json @@ -410,6 +410,29 @@ } ] }, + { + "path": "data_analysis", + "title": { + "zh": "数据分析", + "en": "data analysis" + }, + "children": [ + { + "path": "pivot_table_dataAnalysis", + "title": { + "zh": "透视表数据分析", + "en": "pivot table data analysis" + } + }, + { + "path": "list_table_dataAnalysis", + "title": { + "zh": "基本表数据分析", + "en": "list table data analysis" + } + } + ] + }, { "path": "components", "title": { diff --git a/docs/assets/guide/zh/data_analysis/list_table_dataAnalysis.md b/docs/assets/guide/zh/data_analysis/list_table_dataAnalysis.md new file mode 100644 index 000000000..c1dd3f9aa --- /dev/null +++ b/docs/assets/guide/zh/data_analysis/list_table_dataAnalysis.md @@ -0,0 +1,141 @@ +# 基础表格数据分析 + +目前支持的能力有排序,过滤,数据聚合计算。 + +# 数据排序 + +具体可参阅教程:https://visactor.io/vtable/guide/basic_function/sort + +# 数据过滤 + +基础表格组件通过接口`updateFilterRules`来设置数据过滤规则,支持值过滤和函数过滤。下面是过滤数据的用法示例: + +```javascript +tableInstance.updateFilterRules([ + { + filterKey: 'sex', + filteredValues: ['boy'] + }, + { + filterFunc: (record: Record) => { + return record.age > 30; + } + } +]); +``` + +在上述示例中,我们通过`filterKey`和`filteredValues`来设置值过滤,只显示性别为"boy"的数据;同时,我们使用了函数过滤,通过`filterFunc`来自定义过滤逻辑,只显示`age`字段即年龄大于 30 的数据。 + +具体示例:https://visactor.io/vtable/demo/list-table-data-analysis/list-table-data-filter + +# 数据聚合 + +基础表格支持对数据进行聚合计算,每一列可以设置不同的聚合方式,包括求和、平均、最大值、最小值,以及自定义函数汇总逻辑。同一列可以设置多种聚合方式,聚合结果会展示在多行。 + +## 聚合计算类型 + +- 求和,设置`aggregationType`为`AggregationType.SUM` +- 平均,设置`aggregationType`为`AggregationType.AVG` +- 最大值,设置`aggregationType`为`AggregationType.MAX` +- 最小值,设置`aggregationType`为`AggregationType.MIN` +- 计数,设置`aggregationType`为`AggregationType.COUNT` +- 自定义函数,设置`aggregationType`为`AggregationType.CUSTOM`,通过`aggregationFun`来设置自定义的聚合逻辑 + +## 聚合值格式化函数 + +通过`formatFun`来设置聚合值的格式化函数,可以自定义聚合值的展示格式。 + +## 聚合结果展示位置 + +通过`showOnTop`来控制聚合结果的展示位置,默认为`false`,即聚合结果展示在 body 的底部。如果设置为`true`,则聚合结果展示在 body 的顶部。 + +注意:目前聚合值没有自定冻结能力,需要结合 bottomFrozonRowCount 来实现固定显示,另外尴尬的是目前还没有增加 topFrozonRowCount,所以建议可以先将聚合结果显示在 body 底部。后续会支持全面的冻结能力。 + +## 聚合配置 + +聚合配置可以在 `columns` 列定义中进行设置,也可以在表格全局 `option` 中配置中。 + +### 列定义中配置聚合方式 + +在列定义中,可以通过`aggregation`属性来配置聚合方式。下面是一个聚合配置的示例: + +```javascript +columns: { + field: 'salary', + title: 'salary', + width: 100, + aggregation: [ + { + aggregationType: AggregationType.MAX, + formatFun(value) { + return '最高薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: AggregationType.MIN, + formatFun(value) { + return '最低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ] +} +``` + +在上述示例中,我们针对`salary`这一列设置了三种聚合方式:最大值、最小值和平均值。通过`aggregationType`来指定聚合方式,然后可以通过`formatFun`来自定义聚合结果的展示格式,通过`showOnTop`来控制将聚合结果展示在 body 的顶部还是底部。 + +### 表格全局配置聚合方式 + +除了在列定义中配置聚合方式,也可以在表格全局配置中进行设置。下面是一个全局配置的示例: + +```javascript +aggregation(args) { + if (args.col === 1) { + return [ + { + aggregationType: AggregationType.MAX, + formatFun(value) { + return '最大ID:' + Math.round(value) + '号'; + } + }, + { + aggregationType: AggregationType.MIN, + showOnTop: false, + formatFun(value, col, row, table) { + return '最小ID:' + Math.round(value) + '号'; + } + } + ]; + } + if (args.field === 'salary') { + return [ + { + aggregationType: AggregationType.MIN, + formatFun(value) { + return '最低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均薪资:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ]; + } + return null; +} +``` + +在上述示例中,我们通过全局配置的`aggregation`函数来设置聚合方式,根据不同的条件返回不同的聚合配置。例如,当`args.col === 1`时,我们设置了最大值和最小值的聚合方式;当`args.field === 'salary'`时,我们设置了最低值和平均值的聚合方式。 + +具体示例可参考:https://visactor.io/vtable/demo/list-table-data-analysis/list-table-aggregation-multiple + +以上就是基础表格数据分析能力的教程文档,涵盖了数据过滤和数据聚合的配置和用法。希望这份文档能对你有所帮助!如有任何疑问,请随时提问。 diff --git a/docs/assets/guide/zh/data_analysis/pivot_table_dataAnalysis.md b/docs/assets/guide/zh/data_analysis/pivot_table_dataAnalysis.md new file mode 100644 index 000000000..f2955e934 --- /dev/null +++ b/docs/assets/guide/zh/data_analysis/pivot_table_dataAnalysis.md @@ -0,0 +1,339 @@ +# 透视数据分析 + +下图中一共有四个业务维度:地区、省份、年份、季度,看数指标:销售额,利润。 +
+ +

透视表结构说明

+
+针对图中销售数据,位置在单元格[5, 5],即列5行5的数据:代表了2016年Q2季度下东北地区黑龙江省的销售利润值。也就是对应到行维度值:['东北', '黑龙江'],列维度:['2016', '2016-Q2'],指标:'利润'。接下来将介绍如何用VTable实现这种多维表格。 + +# VTable实现多维表格 +## 概念映射到配置项 +上图透视表的配置如下: +``` +const option={ + rows:['region','province'], //行维度 + columns:['year','quarter'], //列维度 + indicators:['sales','profit'], //指标 + enableDataAnalysis: true, //是否开启数据分析功能 + records:[ //数据源 如果传入了汇总数据则使用用户传入数据 + { + region:'东北', + province:'黑龙江', + year:'2016', + quarter:'2016-Q1', + sales:1243, + profit:546 + }, + ... + ] +} +``` + +该配置是多维表格最简配置。随着对功能要求的复杂性可以针对各功能点来添加各项配置来满足需求。 +## 数据分析相关配置: +|配置项|类型|描述| +|:----|:----|:----| +|rows|(IRowDimension \| string)[]|行维度字段数组,用于解析出对应的维度成员| +|columns|(IColumnDimension \| string)[]|列维度字段数组,用于解析出对应的维度成员| +|indicators|(IIndicator \| string)[]|具体展示指标| +|dataConfig.aggregationRules|aggregationRule[]|按照行列维度聚合值计算规则| +|dataConfig.derivedFieldRules|DerivedFieldRule[]|派生字段| +|dataConfig.sortRules|sortRule[]|排序规则| +|dataConfig.filterRules|filterRule[]|过滤规则| +|dataConfig.totals|totalRule[]|小计或总计| + +dataConfig配置定义: +``` +/** + * 数据处理配置 + */ +export interface IDataConfig { + aggregationRules?: AggregationRules; //按照行列维度聚合值计算规则; + sortRules?: SortRules; //排序规则; + filterRules?: FilterRules; //过滤规则; + totals?: Totals; //小计或总计; + derivedFieldRules?: DerivedFieldRules; //派生字段定义 + ... +} +``` +dataConfig 应用举例: +### 1. 数据汇总规则 +[option说明](../../../option/PivotTable#dataConfig.totals) +配置示例: +``` +dataConfig: { + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['province'], + grandTotalLabel: '行总计', + subTotalLabel: '小计', + showGrandTotalsOnTop: true //汇总值显示在上 + }, + column: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['quarter'], + grandTotalLabel: '列总计', + subTotalLabel: '小计' + } + } + }, +``` +具体示例:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-total +### 2. 排序规则 +[option说明](../../../option/PivotTable#dataConfig.sortRules) +配置示例: +``` + sortRules: [ + { + sortField: 'city', + sortByIndicator: 'sales', + sortType: VTable.TYPES.SortType.DESC, + query: ['办公用品', '笔'] + } as VTable.TYPES.SortByIndicatorRule + ] + +``` + +如果需要修改排序规则 透视表可以使用接口 `updateSortRules`。 + +具体示例:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-sort-dimension +### 3. 过滤规则 +[option说明](../../../option/PivotTable#dataConfig.filterRules) +配置示例: +``` +filterRules: [ + { + filterFunc: (record: Record) => { + return record.province !== '四川省' || record.category !== '家具'; + } + } + ] +``` +具体示例:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-filter +### 4. 聚合方式 +[option说明](../../../option/PivotTable#dataConfig.aggregationRules) +配置示例: +``` + aggregationRules: [ + //做聚合计算的依据,如销售额如果没有配置则默认按聚合sum计算结果显示单元格内容 + { + indicatorKey: 'TotalSales', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.SUM, //计算类型 + formatFun: sumNumberFormat + }, + { + indicatorKey: 'OrderCount', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.COUNT, //计算类型 求数量 + formatFun: countNumberFormat + }, + { + indicatorKey: 'AverageOrderSales', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.AVG, //计算类型 求平均 + }, + { + indicatorKey: 'MaxOrderSales', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.MAX, //计算类型 求最大 + }, + { + indicatorKey: 'OrderSalesValue', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.NONE, //不做聚合 匹配到其中对应数据获取其对应field的值 + } + ] +``` +具体示例:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-aggregation +### 5. 派生字段 +[option说明](../../../option/PivotTable#dataConfig.derivedFieldRules) +配置示例: +``` + derivedFieldRules: [ + { + fieldName: 'Year', + derivedFunc: VTable.DataStatistics.dateFormat('Order Date', '%y', true), + }, + { + fieldName: 'Month', + derivedFunc: VTable.DataStatistics.dateFormat('Order Date', '%n', true), + } + ] +``` +具体示例:https://visactor.io/vtable/demo/data-analysis/pivot-analysis-derivedField +## 数据分析过程 +依赖配置:维度,指标及dataConfig。 +### 遍历数据的流程: +遍历一遍records,解析出行列表头维度值用于展示表头单元格,将records中所有数据分配到对应的行列路径集合中并计算出body部分指标单元格的聚合值。 +
+ +

数据分析过程

+
+ +### 数据维度tree +根据上述遍历的结构,将产生一棵维度树,从这棵树可以查找到单元格的值及值的原始数据条目。 +
+ +

组织维度树聚合数据

+
+ 经过对record分组聚合的分析计算,最终呈现到表格中单元格数据和records数据源的对应关系: +
+ +

数据源条目和单元格的对应关系

+
+ +### 自定义维度树 +虽然具有分析能力的多维表格可以自动分析各个维度的维度值组成行列表头的树形结构,并且可以根据`dataConfig.sortRules`进行排序,但具有复杂业务逻辑的场景还是期望可以能够**自定义行列表头维度值**及顺序。那么可以通过rowTree和columnTree来实现这些业务需求场景。 +- enableDataAnalysis需设置为false来关闭VTable内部聚合数据的分析,提升一定的性能。 + +
+ +

custom rowTree columnTree

+
+ +自定义树的配置: +``` +const option = { + rowTree: [{ + dimensionKey: 'region', + value: '中南', + children: [ + { + dimensionKey: 'province', + value: '广东', + }, + { + dimensionKey: 'province', + value: '广西', + } + ] + }, + { + dimensionKey: 'region', + value: '华东', + children: [ + { + dimensionKey: 'province', + value: '上海', + }, + { + dimensionKey: 'province', + value: '山东', + } + ] + }], + columnTree: [{ + dimensionKey: 'year', + value: '2016', + children: [ + { + dimensionKey: 'quarter', + value: '2016-Q1', + children: [ + { + indicatorKey: 'sales', + value: 'sales' + }, + { + indicatorKey: 'profit', + value: 'profit' + } + ] + }, + { + dimensionKey: 'quarter', + value: '2016-Q2', + children: [ + { + indicatorKey: 'sales', + value: 'sales' + }, + { + indicatorKey: 'profit', + value: 'profit' + } + ] + } + ] + }], + indicators: ['sales', 'profit'], + //enableDataAnalysis:true, + corner: { + titleOnDimension: 'none' + }, + records: [ + { + region: '中南', + province: '广东', + year: '2016', + quarter: '2016-Q1', + sales: 1243, + profit: 546 + }, + { + region: '中南', + province: '广东', + year: '2016', + quarter: '2016-Q2', + sales: 2243, + profit: 169 + }, { + region: '中南', + province: '广西', + year: '2016', + quarter: '2016-Q1', + sales: 3043, + profit: 1546 + }, + { + region: '中南', + province: '广西', + year: '2016', + quarter: '2016-Q2', + sales: 1463, + profit: 609 + }, + { + region: '华东', + province: '上海', + year: '2016', + quarter: '2016-Q1', + sales: 4003, + profit: 1045 + }, + { + region: '华东', + province: '上海', + year: '2016', + quarter: '2016-Q2', + sales: 5243, + profit: 3169 + }, { + region: '华东', + province: '山东', + year: '2016', + quarter: '2016-Q1', + sales: 4543, + profit: 3456 + }, + { + region: '华东', + province: '山东', + year: '2016', + quarter: '2016-Q2', + sales: 6563, + profit: 3409 + } + ] +}; +``` +VTable官网示例:https://visactor.io/vtable/demo/table-type/pivot-table. + +自定义树的复杂在于组建行列维度树,可酌情根据业务场景来选择使用,如果具有复杂的排序、汇总或分页规则可选择使用自定义方式。 + +**注意:如果选择自定义树的配置方式将不开启VTable内部的数据聚合能力,即匹配到的数据条目中的某一条作为单元格指标值。** \ No newline at end of file diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 824784067..bc70f12aa 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -954,6 +954,16 @@ export class PivotTable extends BaseTable implements PivotTableAPI { getHierarchyState(col: number, row: number): HierarchyState { return this._getHeaderLayoutMap(col, row)?.hierarchyState; } + /** 获取列头树结构 */ + getLayoutColumnTree(): LayouTreeNode[] { + const layoutMap = this.internalProps.layoutMap; + return layoutMap.getLayoutColumnTree(); + } + /** 获取表格列头树形结构的占位的总节点数 */ + getLayoutColumnTreeCount(): number { + const layoutMap = this.internalProps.layoutMap; + return layoutMap.getLayoutColumnTreeCount(); + } /** 获取行头树结构 */ getLayoutRowTree(): LayouTreeNode[] { const layoutMap = this.internalProps.layoutMap; diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index 14b597ced..32e2220ab 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -2818,13 +2818,25 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { return indicatorInfo; } /** 获取行头树结构 */ + getLayoutColumnTree() { + const tree: LayouTreeNode[] = []; + const children = this.columnDimensionTree.tree.children; + generateLayoutTree(tree, children); + return tree; + } + /** 获取行头树结构 */ getLayoutRowTree() { const tree: LayouTreeNode[] = []; const children = this.rowDimensionTree.tree.children; generateLayoutTree(tree, children); return tree; } - + /** 获取列头总共的行数(全部展开情况下) */ + getLayoutColumnTreeCount() { + const children = this.columnDimensionTree.tree.children; + const mainTreeCount = countLayoutTree(children, this.rowHierarchyType === 'tree'); + return mainTreeCount; + } /** 获取行头总共的行数(全部展开情况下) */ getLayoutRowTreeCount() { const children = this.rowDimensionTree.tree.children; From 58c85ab682b8b82b4a81a5dc99290a4f9975476d Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 19 Feb 2024 19:26:45 +0800 Subject: [PATCH 24/63] docs: add api and option for aggregation --- docs/assets/api/en/methods.md | 93 +++++++++-- docs/assets/api/zh/methods.md | 152 +++++++++++++----- .../option/en/column/base-column-type.md | 30 +++- docs/assets/option/en/table/listTable.md | 36 +++++ .../option/zh/column/base-column-type.md | 19 ++- docs/assets/option/zh/table/listTable.md | 52 ++++-- packages/vtable/src/data/DataSource.ts | 3 - 7 files changed, 308 insertions(+), 77 deletions(-) diff --git a/docs/assets/api/en/methods.md b/docs/assets/api/en/methods.md index 9e1b07ee2..64d22e11a 100644 --- a/docs/assets/api/en/methods.md +++ b/docs/assets/api/en/methods.md @@ -13,6 +13,7 @@ Update table configuration items, which will be automatically redrawn after bein */ updateOption(options: BaseTableConstructorOptions) => void ``` + If you need to update a single configuration item, please refer to the other `update**` interfaces below ## updateTheme(Function) @@ -26,15 +27,20 @@ Update the table theme and it will be automatically redrawn after calling it. */ updateTheme(theme: ITableThemeDefine) => void ``` + use: + ``` tableInstance.updateTheme(newTheme) ``` + Corresponding attribute update interface(https://visactor.io/vtable/guide/basic_function/update_option): + ``` // will not automatically redraw after calling tableInstance.theme = newTheme; ``` + ## updateColumns(Function) Update the configuration information of the columns field of the table, and it will be automatically redrawn after calling @@ -46,15 +52,20 @@ Update the configuration information of the columns field of the table, and it w */ updateColumns(columns: ColumnsDefine) => void ``` + use: + ``` tableInstance. updateColumns(newColumns) ``` + Corresponding attribute update interface(https://visactor.io/vtable/guide/basic_function/update_option): + ``` // will not automatically redraw after calling tableInstance.columns = newColumns; ``` + ## updatePagination(Function) Update page number configuration information, and it will be automatically redrawn after calling @@ -66,7 +77,9 @@ Update page number configuration information, and it will be automatically redra */ updatePagination(pagination: IPagination): void; ``` + IPagination type define: + ``` /** *Paging configuration @@ -80,14 +93,17 @@ export interface IPagination { currentPage?: number; } ``` + The basic table and VTable data analysis pivot table support paging, but the pivot combination chart does not support paging. Note! The perPageCount in the pivot table will be automatically corrected to an integer multiple of the number of indicators. ## renderWithRecreateCells(Function) + Re-collect the cell objects and re-render the table. Use scenarios such as: Refresh after batch updating multiple configuration items: + ``` tableInstance.theme = newThemeObj; tableInstance.widthMode = 'autoWidth'; @@ -128,8 +144,10 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** The basic t ``` ## getDrawRange(Function) + Get the boundRect value of the actual drawn content area of the table like + ``` { "bounds": { @@ -146,6 +164,7 @@ like width: 1580 } ``` + ## selectCell(Function) Select a cell. If empty is passed, the currently selected highlight state will be cleared. @@ -176,6 +195,7 @@ Select one or more cell ranges ``` ## getSelectedCellInfos(Function) + Get the selected cell information, and the returned result is a two-dimensional array. The first-level array item represents a row, and each item of the second-level array represents a cell information of the row. ``` @@ -234,6 +254,7 @@ Getting the style of a cell */ getCellStyle(col: number, row: number) => CellStyle ``` + ## getRecordByCell(Function) Get the data item of this cell @@ -256,6 +277,7 @@ Get the column index and row index in the body part according to the row and col /** Get the column index and row index in the body part based on the row and column numbers of the table cells */ getBodyIndexByTableIndex: (col: number, row: number) => CellAddress; ``` + ## getTableIndexByBodyIndex(Function) Get the row and column number of the cell based on the column index and row index of the body part @@ -266,14 +288,15 @@ Get the row and column number of the cell based on the column index and row inde ``` ## getTableIndexByRecordIndex(Function) -Get the index row number or column number displayed in the table based on the index of the data source (Related to transposition, the non-transposition obtains the row number, and the transposed table obtains the column number). + +Get the index row number or column number displayed in the table based on the index of the data source (Related to transposition, the non-transposition obtains the row number, and the transposed table obtains the column number). Note: ListTable specific interface ``` /** - * Get the index row number or column number displayed in the table based on the index of the data source (Related to transposition, the non-transposition obtains the row number, and the transposed table obtains the column number). - + * Get the index row number or column number displayed in the table based on the index of the data source (Related to transposition, the non-transposition obtains the row number, and the transposed table obtains the column number). + Note: ListTable specific interface * @param recordIndex */ @@ -281,25 +304,27 @@ Note: ListTable specific interface ``` ## getTableIndexByField(Function) + Get the index row number or column number displayed in the table according to the field of the data source (Related to transposition, the non-transposition obtains the row number, and the transposed table obtains the column number). - Note: ListTable specific interface +Note: ListTable specific interface + ``` /** - * Get the index row number or column number displayed in the table according to the field of the data source (Related to transposition, the non-transposition obtains the row number, and the transposed table obtains the column number). - + * Get the index row number or column number displayed in the table according to the field of the data source (Related to transposition, the non-transposition obtains the row number, and the transposed table obtains the column number). + Note: ListTable specific interface * @param recordIndex */ getTableIndexByField: (field: FieldDef) => number; ``` - ## getRecordShowIndexByCell(Function) Get the index of the current cell data in the body part, that is, remove the index of the header level number by the row and column number.(Related to transpose, the non-transpose gets the body row number, and the transpose table gets the body column number) ** ListTable proprietary ** + ``` /** Get the display index of the current cell in the body part,it is ( row / col )- headerLevelCount. Note: ListTable specific interface */ getRecordShowIndexByCell(col: number, row: number): number @@ -307,9 +332,10 @@ Get the index of the current cell data in the body part, that is, remove the ind ## getCellAddrByFieldRecord(Function) -Get the cell row and column number based on the index and field in the data source. +Get the cell row and column number based on the index and field in the data source. Note: ListTable specific interface + ``` /** * Get the cell row and column number based on the index and field in the data source. Note: ListTable specific interface @@ -319,6 +345,7 @@ Note: ListTable specific interface */ getCellAddrByFieldRecord: (field: FieldDef, recordIndex: number) => CellAddress; ``` + ## getCellOriginRecord(Function) Get the source data item of this cell. @@ -405,7 +432,9 @@ Get the text of the cell with omitted text. ``` ## getCellRect(Function) + Get the specific position of the cell in the entire table. + ``` /** * Get the range of cells. The return value is Rect type. Regardless of whether it is a merged cell, the coordinates start from 0 @@ -417,7 +446,9 @@ Get the specific position of the cell in the entire table. ``` ## getCellRelativeRect(Function) + Get the specific position of the cell in the entire table. Relative position is based on the upper left corner of the table (scroll condition minus scroll value) + ``` /** * The obtained position is relative to the upper left corner of the table display interface. In case of scrolling, if the cell has rolled out of the top of the table, the y of this cell will be a negative value. @@ -444,7 +475,6 @@ Get the path to the row list header {{ use: ICellHeaderPaths() }} - ## getCellHeaderTreeNodes(Function) Obtain the header tree node based on the row and column number, which includes the user's custom attributes on the custom tree rowTree and columnTree trees (it is also the node of the internal layout tree, please do not modify it at will after obtaining it).Under normal circumstances, just use getCellHeaderPaths. @@ -492,28 +522,37 @@ For pivot table interfaces, get specific cell addresses based on the header dime | IDimensionInfo[] ) => CellAddress ``` + ## getCheckboxState(Function) + Get the selected status of all data in the checkbox under a certain field. The order corresponds to the original incoming data records. It does not correspond to the status value of the row displayed in the table. + ``` getCheckboxState(field?: string | number): Array ``` ## getCellCheckboxState(Function) + Get the status of a cell checkbox + ``` getCellCheckboxState(col: number, row: number): Array ``` ## getScrollTop(Function) + Get the current vertical scroll position ## getScrollLeft(Function) + Get the current horizontal scroll position ## setScrollTop(Function) + Set the vertical scroll position (the rendering interface will be updated) ## setScrollLeft(Function) + Set the horizontal scroll position (the rendering interface will be updated) ## scrollToCell(Function) @@ -527,8 +566,11 @@ Scroll to a specific cell location */ scrollToCell(cellAddr: { col?: number; row?: number })=>void ``` + ## toggleHierarchyState(Function) + Tree expand and collapse state switch + ``` /** * Header switches level status @@ -537,8 +579,11 @@ Tree expand and collapse state switch */ toggleHierarchyState(col: number, row: number) ``` + ## getHierarchyState(Function) + Get the tree-shaped expanded or collapsed status of a certain cell + ``` /** * Get the collapsed and expanded status of hierarchical nodes @@ -554,10 +599,13 @@ enum HierarchyState { none = 'none' } ``` + ## getLayoutRowTree(Function) + ** PivotTable Proprietary ** Get the table row header tree structure + ``` /** * Get the table row tree structure @@ -567,11 +615,13 @@ Get the table row header tree structure ``` ## getLayoutRowTreeCount(Function) + ** PivotTable Proprietary ** Get the total number of nodes occupying the table row header tree structure. Note: The logic distinguishes between flat and tree hierarchies. + ``` /** * Get the total number of nodes occupying the table row header tree structure. @@ -592,9 +642,11 @@ Update the sort status, ListTable exclusive */ updateSortState(sortState: SortState[] | SortState | null, executeSort: boolean = true) ``` + ## updateSortRules(Function) Pivot table update sorting rules, exclusive to PivotTable + ``` /** * Full update of sorting rules @@ -745,6 +797,7 @@ Export a picture of a certain cell range ``` ## changeCellValue(Function) + Change the value of a cell: ``` @@ -753,6 +806,7 @@ Change the value of a cell: ``` ## changeCellValues(Function) + Change the value of cells in batches: ``` @@ -797,13 +851,14 @@ End editing Get all data of the current table ## dataSouce(CachedDataSource) + Set the data source for the VTable table component instance. For specific usage, please refer to [Asynchronous data lazy loading demo](../demo/performance/async-data) and [Tutorial](../guide/data/async_data) ## addRecords(Function) - Add data, support multiple pieces of data +Add data, support multiple pieces of data -** Note: ListTable specific interface ** +** Note: ListTable specific interface ** ``` /** @@ -818,9 +873,9 @@ Set the data source for the VTable table component instance. For specific usage, ## addRecord(Function) - Add data, single piece of data +Add data, single piece of data -** Note: ListTable specific interface ** +** Note: ListTable specific interface ** ``` /** @@ -837,7 +892,7 @@ Set the data source for the VTable table component instance. For specific usage, Delete data supports multiple pieces of data -** Note: ListTable specific interface ** +** Note: ListTable specific interface ** ``` /** @@ -846,11 +901,13 @@ Delete data supports multiple pieces of data */ deleteRecords(recordIndexs: number[]) ``` + ## updateRecords(Function) Modify data to support multiple pieces of data ** ListTable proprietary ** + ``` /** * Modify data to support multiple pieces of data @@ -872,10 +929,12 @@ Get the display cell range of the table body part ## getBodyVisibleColRange(Function) Get the displayed column number range in the body part of the table + ``` /** Get the displayed column number range in the body part of the table */ getBodyVisibleColRange: () => { colStart: number; colEnd: number }; ``` + ## getBodyVisibleRowRange(Function) Get the displayed row number range of the table body part @@ -883,4 +942,8 @@ Get the displayed row number range of the table body part ``` /** Get the displayed row number range of the table body */ getBodyVisibleRowRange: () => { rowStart: number; rowEnd: number }; -``` \ No newline at end of file +``` + +## getAggregateValuesByField(Function) + +Get aggregate value diff --git a/docs/assets/api/zh/methods.md b/docs/assets/api/zh/methods.md index 3fdfb03fd..72bc0a6e5 100644 --- a/docs/assets/api/zh/methods.md +++ b/docs/assets/api/zh/methods.md @@ -13,6 +13,7 @@ */ updateOption(options: BaseTableConstructorOptions) => void ``` + 如果需要更新单个配置项,请参考下面其他`update**`接口 ## updateTheme(Function) @@ -26,18 +27,23 @@ */ updateTheme(theme: ITableThemeDefine) => void ``` + 使用: + ``` tableInstance.updateTheme(newTheme) ``` + 对应属性更新接口(可参考教程:https://visactor.io/vtable/guide/basic_function/update_option): + ``` // 调用后不会自动重绘 tableInstance.theme = newTheme; ``` + ## updateColumns(Function) -更新表格的columns字段配置信息,调用后会自动重绘。 +更新表格的 columns 字段配置信息,调用后会自动重绘。 ```ts /** @@ -46,15 +52,20 @@ tableInstance.theme = newTheme; */ updateColumns(columns: ColumnsDefine) => void ``` + 使用: + ``` tableInstance.updateColumns(newColumns) ``` + 对应属性更新接口(可参考教程:https://visactor.io/vtable/guide/basic_function/update_option): + ``` // 调用后不会自动重绘 tableInstance.columns = newColumns; ``` + ## updatePagination(Function) 更新页码配置信息 调用后会自动重绘。 @@ -66,7 +77,9 @@ tableInstance.columns = newColumns; */ updatePagination(pagination: IPagination): void; ``` + 其中类型: + ``` /** * 分页配置 @@ -80,14 +93,17 @@ export interface IPagination { currentPage?: number; } ``` -基本表格和VTable数据分析透视表支持分页,透视组合图不支持分页。 -注意! 透视表中perPageCount会自动修正为指标数量的整数倍。 +基本表格和 VTable 数据分析透视表支持分页,透视组合图不支持分页。 + +注意! 透视表中 perPageCount 会自动修正为指标数量的整数倍。 ## renderWithRecreateCells(Function) + 重新组织单元格对象树并重新渲染表格,使用场景如: 批量更新多个配置项后的刷新: + ``` tableInstance.theme = newThemeObj; tableInstance.widthMode = 'autoWidth'; @@ -119,7 +135,7 @@ tableInstance.renderWithRecreateCells(); ## setRecords(Function) 设置表格数据接口,可作为更新接口调用。 -** 基本表格可同时设置排序状态对表格数据排序,sort设置为空清空排序状态,如果不设置则按当前排序状态对传入数据排序 ** +** 基本表格可同时设置排序状态对表格数据排序,sort 设置为空清空排序状态,如果不设置则按当前排序状态对传入数据排序 ** ``` setRecords(records: Array) //透视表 @@ -127,8 +143,10 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ``` ## getDrawRange(Function) -获取表格实际绘制内容区域的boundRect的值 + +获取表格实际绘制内容区域的 boundRect 的值 如 + ``` { "bounds": { @@ -145,6 +163,7 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 width: 1580 } ``` + ## selectCell(Function) 选中某个单元格。如果传空,则清除当前选中高亮状态。 @@ -169,6 +188,7 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 */ selectCells(cellRanges: CellRange[]): void ``` + 其中: {{ use: CellRange() }} @@ -200,7 +220,7 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ## getCellOriginValue(Function) -获取单元格展示数据的format前的值 +获取单元格展示数据的 format 前的值 ``` /** @@ -232,6 +252,7 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 */ getCellStyle(col: number, row: number) => CellStyle ``` + ## getRecordByCell(Function) 获取该单元格的数据项 @@ -248,15 +269,16 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ## getBodyIndexByTableIndex(Function) -根据表格单元格的行列号 获取在body部分的列索引及行索引 +根据表格单元格的行列号 获取在 body 部分的列索引及行索引 ``` /** 根据表格单元格的行列号 获取在body部分的列索引及行索引 */ getBodyIndexByTableIndex: (col: number, row: number) => CellAddress; ``` + ## getTableIndexByBodyIndex(Function) -根据body部分的列索引及行索引,获取单元格的行列号 +根据 body 部分的列索引及行索引,获取单元格的行列号 ``` /** 根据body部分的列索引及行索引,获取单元格的行列号 */ @@ -264,14 +286,15 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ``` ## getTableIndexByRecordIndex(Function) -根据数据源的index 获取显示到表格中的index 行号或者列号(与转置相关,非转置获取的是行号,转置表获取的是列号)。 -** ListTable 专有 ** +根据数据源的 index 获取显示到表格中的 index 行号或者列号(与转置相关,非转置获取的是行号,转置表获取的是列号)。 + +** ListTable 专有 ** ``` /** * 根据数据源的index 获取显示到表格中的index 行号或者列号(与转置相关,非转置获取的是行号,转置表获取的是列号)。 - + 注:ListTable特有接口 * @param recordIndex */ @@ -279,9 +302,11 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ``` ## getTableIndexByField(Function) -根据数据源的field 获取显示到表格中的index 行号或者列号(与转置相关,非转置获取的是行号,转置表获取的是列号)。 -** ListTable 专有 ** +根据数据源的 field 获取显示到表格中的 index 行号或者列号(与转置相关,非转置获取的是行号,转置表获取的是列号)。 + +** ListTable 专有 ** + ``` /** * 根据数据源的field 获取显示到表格中的index 行号或者列号(与转置相关,非转置获取的是行号,转置表获取的是列号)。注:ListTable特有接口 @@ -290,12 +315,12 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 getTableIndexByField: (field: FieldDef) => number; ``` - ## getRecordShowIndexByCell(Function) -获取当前单元格数据在body部分的索引,即通过行列号去除表头层级数的索引(与转置相关,非转置获取的是body行号,转置表获取的是body列号)。 +获取当前单元格数据在 body 部分的索引,即通过行列号去除表头层级数的索引(与转置相关,非转置获取的是 body 行号,转置表获取的是 body 列号)。 + +** ListTable 专有 ** -** ListTable 专有 ** ``` /** 获取当前单元格在body部分的展示索引,即( row / col )- headerLevelCount。注:ListTable特有接口 */ getRecordShowIndexByCell(col: number, row: number): number @@ -303,9 +328,10 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ## getCellAddrByFieldRecord(Function) -根据数据源中的index和field获取单元格行列号。 +根据数据源中的 index 和 field 获取单元格行列号。 + +注:ListTable 特有接口 -注:ListTable特有接口 ``` /** * 根据数据源中的index和field获取单元格行列号。注:ListTable特有接口 @@ -315,6 +341,7 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 */ getCellAddrByFieldRecord: (field: FieldDef, recordIndex: number) => CellAddress; ``` + ## getCellOriginRecord(Function) 获取该单元格的源数据项。 @@ -332,6 +359,7 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 */ getCellOriginRecord(col: number, row: number) ``` + ## getAllCells(Function) 获取所有单元格上下文信息 @@ -400,7 +428,9 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ``` ## getCellRect(Function) + 获取单元格在整张表格中的具体位置。 + ``` /** * 获取单元格的范围 返回值为Rect类型。不考虑是否为合并单元格的情况,坐标从0开始 @@ -412,7 +442,9 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ``` ## getCellRelativeRect(Function) + 获取单元格在整张表格中的具体位置。相对位置是基于表格左上角(滚动情况减去滚动值) + ``` /** * 获取的位置是相对表格显示界面的左上角 情况滚动情况 如单元格已经滚出表格上方 则这个单元格的y将为负值 @@ -441,7 +473,7 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ## getCellHeaderTreeNodes(Function) -根据行列号获取表头tree节点,包含了用户在自定义树rowTree及columnTree树上的自定义属性(也是内部布局树的节点,获取后请不要随意修改)。一般情况下用getCellHeaderPaths即可。 +根据行列号获取表头 tree 节点,包含了用户在自定义树 rowTree 及 columnTree 树上的自定义属性(也是内部布局树的节点,获取后请不要随意修改)。一般情况下用 getCellHeaderPaths 即可。 ``` /** @@ -455,7 +487,7 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ## getCellAddress(Function) -根据数据和 field 属性字段名称获取 body 中某条数据的行列号。目前仅支持基本表格ListTable +根据数据和 field 属性字段名称获取 body 中某条数据的行列号。目前仅支持基本表格 ListTable ``` /** @@ -488,32 +520,41 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 ``` ## getCheckboxState(Function) -获取某个字段下checkbox 全部数据的选中状态 顺序对应原始传入数据records 不是对应表格展示row的状态值 + +获取某个字段下 checkbox 全部数据的选中状态 顺序对应原始传入数据 records 不是对应表格展示 row 的状态值 + ``` getCheckboxState(field?: string | number): Array ``` ## getCellCheckboxState(Function) -获取某个单元格checkbox的状态 + +获取某个单元格 checkbox 的状态 + ``` getCellCheckboxState(col: number, row: number): Array ``` + ## getScrollTop(Function) + 获取当前竖向滚动位置 ## getScrollLeft(Function) + 获取当前横向滚动位置 ## setScrollTop(Function) + 设置竖向滚动位置 (会更新渲染界面) ## setScrollLeft(Function) + 设置横向滚动位置(会更新渲染界面) ## scrollToCell(Function) 滚动到具体某个单元格位置。 -col或者row可以为空,为空的话也就是只移动x方向或者y方向。 +col 或者 row 可以为空,为空的话也就是只移动 x 方向或者 y 方向。 ``` /** @@ -522,18 +563,24 @@ col或者row可以为空,为空的话也就是只移动x方向或者y方向。 */ scrollToCell(cellAddr: { col?: number; row?: number })=>void ``` + ## toggleHierarchyState(Function) + 树形展开收起状态切换 + ``` /** * 表头切换层级状态 * @param col * @param row */ - toggleHierarchyState(col: number, row: number) + toggleHierarchyState(col: number, row: number) ``` + ## getHierarchyState(Function) -获取某个单元格树形展开or收起状态 + +获取某个单元格树形展开 or 收起状态 + ``` /** * 获取层级节点收起展开的状态 @@ -551,9 +598,11 @@ enum HierarchyState { ``` ## getLayoutRowTree(Function) -** PivotTable 专有 ** + +** PivotTable 专有 ** 获取表格行头树形结构 + ``` /** * 获取表格行树状结构 @@ -563,11 +612,13 @@ enum HierarchyState { ``` ## getLayoutRowTreeCount(Function) -** PivotTable 专有 ** + +** PivotTable 专有 ** 获取表格行头树形结构的占位的总节点数。 注意:逻辑中区分了平铺和树形层级结构 + ``` /** * 获取表格行头树形结构的占位的总节点数。 @@ -588,9 +639,11 @@ enum HierarchyState { */ updateSortState(sortState: SortState[] | SortState | null, executeSort: boolean = true) ``` + ## updateSortRules(Function) 透视表更新排序规则,PivotTable 专有 + ``` /** * 全量更新排序规则 @@ -741,7 +794,8 @@ use case: 点击图例项后 更新过滤规则 来更新图表 ``` ## changeCellValue(Function) -更改单元格的value值: + +更改单元格的 value 值: ``` /** 设置单元格的value值,注意对应的是源数据的原始值,vtable实例records会做对应修改 */ @@ -749,7 +803,8 @@ use case: 点击图例项后 更新过滤规则 来更新图表 ``` ## changeCellValues(Function) -批量更改单元格的value: + +批量更改单元格的 value: ``` /** @@ -758,7 +813,7 @@ use case: 点击图例项后 更新过滤规则 来更新图表 * @param row 粘贴数据的起始行号 * @param values 多个单元格的数据数组 */ - changeCellValues(startCol: number, startRow: number, values: string[][]) + changeCellValues(startCol: number, startRow: number, values: string[][]) ``` ## getEditor(Function) @@ -793,13 +848,15 @@ use case: 点击图例项后 更新过滤规则 来更新图表 获取当前表格的全部数据 ## dataSouce(CachedDataSource) -给VTable表格组件实例设置数据源,具体使用可以参考[异步懒加载数据demo](../demo/performance/async-data)及[教程](../guide/data/async_data) + +给 VTable 表格组件实例设置数据源,具体使用可以参考[异步懒加载数据 demo](../demo/performance/async-data)及[教程](../guide/data/async_data) ## addRecords(Function) - 添加数据,支持多条数据 - -** ListTable 专有 ** +添加数据,支持多条数据 + +** ListTable 专有 ** + ``` /** * 添加数据 支持多条数据 @@ -808,14 +865,15 @@ use case: 点击图例项后 更新过滤规则 来更新图表 * 如果设置了排序规则recordIndex无效,会自动适应排序逻辑确定插入顺序。 * recordIndex 可以通过接口getRecordShowIndexByCell获取 */ - addRecords(records: any[], recordIndex?: number) + addRecords(records: any[], recordIndex?: number) ``` ## addRecord(Function) - 添加数据,单条数据 +添加数据,单条数据 + +** ListTable 专有 ** -** ListTable 专有 ** ``` /** * 添加数据 单条数据 @@ -831,20 +889,22 @@ use case: 点击图例项后 更新过滤规则 来更新图表 删除数据 支持多条数据 -** ListTable 专有 ** +** ListTable 专有 ** + ``` /** * 删除数据 支持多条数据 * @param recordIndexs 要删除数据的索引(显示到body中的条目索引) */ - deleteRecords(recordIndexs: number[]) + deleteRecords(recordIndexs: number[]) ``` ## updateRecords(Function) 修改数据 支持多条数据 -** ListTable 专有 ** +** ListTable 专有 ** + ``` /** * 修改数据 支持多条数据 @@ -856,7 +916,7 @@ use case: 点击图例项后 更新过滤规则 来更新图表 ## getBodyVisibleCellRange(Function) -获取表格body部分的显示单元格范围 +获取表格 body 部分的显示单元格范围 ``` /** 获取表格body部分的显示单元格范围 */ @@ -865,16 +925,22 @@ use case: 点击图例项后 更新过滤规则 来更新图表 ## getBodyVisibleColRange(Function) -获取表格body部分的显示列号范围 +获取表格 body 部分的显示列号范围 + ``` /** 获取表格body部分的显示列号范围 */ getBodyVisibleColRange: () => { colStart: number; colEnd: number }; ``` + ## getBodyVisibleRowRange(Function) -获取表格body部分的显示行号范围 +获取表格 body 部分的显示行号范围 ``` /** 获取表格body部分的显示行号范围 */ getBodyVisibleRowRange: () => { rowStart: number; rowEnd: number }; ``` + +## getAggregateValuesByField(Function) + +获取聚合值 diff --git a/docs/assets/option/en/column/base-column-type.md b/docs/assets/option/en/column/base-column-type.md index 2c6300d4a..e4e22aab0 100644 --- a/docs/assets/option/en/column/base-column-type.md +++ b/docs/assets/option/en/column/base-column-type.md @@ -11,6 +11,7 @@ ${prefix} field(string) ${prefix} fieldFormat(FieldFormat) Configure data formatting + ``` type FieldFormat = (record: any) => any; ``` @@ -45,9 +46,11 @@ Header cell style, configuration options are slightly different depending on the ${prefix} style Body cell style, type declaration: + ``` style?: IStyleOption | ((styleArg: StylePropertyFunctionArg) => IStyleOption); ``` + {{ use: common-StylePropertyFunctionArg() }} The type structure of IStyleOption is as follows: @@ -65,6 +68,7 @@ Header cell icon configuration. Available configuration types are: ``` string | ColumnIconOption | (string | ColumnIconOption)[]; ``` + For the specific configuration of ColumnIconOption, refer to the [definition](/zh/option.html#ListTable-columns-text.icon.ColumnIconOption定义:) ${prefix} icon(string|Object|Array|Funciton) @@ -80,9 +84,11 @@ icon?: ``` #${prefix} ColumnIconOption definition: + ``` type ColumnIconOption = ImageIcon | SvgIcon; ``` + #${prefix} ImageIcon(Object) {{ use: image-icon( prefix = '##' + ${prefix}) }} @@ -120,6 +126,7 @@ ${prefix} headerCustomRender(Function|Object) Custom rendering of header cell, in function or object form. The type is: `ICustomRenderFuc | ICustomRenderObj`. The definition of ICustomRenderFuc is: + ``` type ICustomRenderFuc = (args: CustomRenderFunctionArg) => ICustomRenderObj; ``` @@ -130,7 +137,6 @@ The definition of ICustomRenderFuc is: prefix = '#' + ${prefix}, ) }} - ${prefix} headerCustomLayout(Function) Custom layout element definition for header cell, suitable for complex layout cell content. @@ -138,6 +144,7 @@ Custom layout element definition for header cell, suitable for complex layout ce ``` (args: CustomRenderFunctionArg) => ICustomLayoutObj; ``` + {{ use: common-CustomRenderFunctionArg() }} {{ use: custom-layout( @@ -148,16 +155,17 @@ ${prefix} customRender(Function|Object) Custom rendering for body cell header cell, in function or object form. The type is: `ICustomRenderFuc | ICustomRenderObj`. The definition of ICustomRenderFuc is: + ``` type ICustomRenderFuc = (args: CustomRenderFunctionArg) => ICustomRenderObj; ``` + {{ use: common-CustomRenderFunctionArg() }} {{ use: common-custom-render-object( prefix = '#' + ${prefix}, ) }} - ${prefix} customLayout(Function) Custom layout element definition for body cell, suitable for complex layout content. @@ -165,6 +173,7 @@ Custom layout element definition for body cell, suitable for complex layout cont ``` (args: CustomRenderFunctionArg) => ICustomLayoutObj; ``` + {{ use: common-CustomRenderFunctionArg() }} {{ use: custom-layout( @@ -182,6 +191,7 @@ Whether to disable column width adjustment. If it is a transposed table or a piv ${prefix} tree (boolean) Whether to display this column as a tree structure, which needs to be combined with the records data structure to be implemented, the nodes that need to be expanded are configured with `children` to accommodate sub-node data. For example: + ``` { "department": "Human Resources Department", @@ -204,15 +214,17 @@ Whether to display this column as a tree structure, which needs to be combined w ${prefix} editor (string|Object|Function) Configure the column cell editor + ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts . +Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts . ${prefix} headerEditor (string|Object|Function) Configure the display title of this column header + ``` headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` @@ -221,4 +233,14 @@ ${prefix} columns (Array) Configure arrays with upper columns, nesting structures to describe column grouping relationships. ${prefix} hideColumnsSubHeader(boolean) = false -Whether to hide the header title of the subtable header. The default value is not hidden. \ No newline at end of file +Whether to hide the header title of the subtable header. The default value is not hidden. + +${prefix} aggregation(Aggregation | CustomAggregation | Array) + +Not required. + +Data aggregation configuration to analyze the column data. + +Global options can also be configured to configure aggregation rules for each column. + +Please refer to the tutorial document diff --git a/docs/assets/option/en/table/listTable.md b/docs/assets/option/en/table/listTable.md index 95b09fb82..1f2603e25 100644 --- a/docs/assets/option/en/table/listTable.md +++ b/docs/assets/option/en/table/listTable.md @@ -74,7 +74,9 @@ order: 'desc' | 'asc' | 'normal'; Global configuration cell editor ``` + editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); + ``` Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts . @@ -82,7 +84,9 @@ ${prefix} headerEditor (string|Object|Function) Global configuration for the editor of the display title in the table header ``` + headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); + ``` {{ use: common-option-secondary( @@ -106,3 +110,35 @@ Drag the table header to move the position. Rules for frozen parts. The default - "disabled" (disables adjusting the position of frozen columns): The headers of other columns are not allowed to be moved into the frozen column, nor are the frozen columns allowed to be moved out. The frozen column remains unchanged. - "adjustFrozenCount" (adjust the number of frozen columns based on the interaction results): allows the headers of other columns to move into the frozen column, and the frozen column to move out, and adjusts the number of frozen columns based on the dragging action. When the headers of other columns are dragged into the frozen column position, the number of frozen columns increases; when the headers of other columns are dragged out of the frozen column position, the number of frozen columns decreases. - "fixedFrozenCount" (can adjust frozen columns and keep the number of frozen columns unchanged): Allows you to freely drag the headers of other columns into or out of the frozen column position while keeping the number of frozen columns unchanged. + +## aggregation(Aggregation|CustomAggregation|Array|Function) + +Data aggregation analysis configuration, global configuration, each column will have aggregation logic, it can also be configured in the column (columns) definition, the configuration in the column has a higher priority. + +``` +aggregation?: + | Aggregation + | CustomAggregation + | (Aggregation | CustomAggregation)[] + | ((args: { + col: number; + field: string; + }) => Aggregation | CustomAggregation | (Aggregation | CustomAggregation)[] | null); +``` + +Among them: + +``` +type Aggregation = { + aggregationType: AggregationType; + showOnTop?: boolean; + formatFun?: (value: number, col: number, row: number, table: BaseTableAPI) => string | number; +}; + +type CustomAggregation = { + aggregationType: AggregationType.CUSTOM; + aggregationFun: (values: any[], records: any[]) => any; + showOnTop?: boolean; + formatFun?: (value: number, col: number, row: number, table: BaseTableAPI) => string | number; +}; +``` diff --git a/docs/assets/option/zh/column/base-column-type.md b/docs/assets/option/zh/column/base-column-type.md index fb7054389..cf9781943 100644 --- a/docs/assets/option/zh/column/base-column-type.md +++ b/docs/assets/option/zh/column/base-column-type.md @@ -214,14 +214,17 @@ ${prefix} tree (boolean) ${prefix} editor (string|Object|Function) 配置该列单元格编辑器 + ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts。 + +其中 IEditor 是@visactor/vtable-editors 中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts。 ${prefix} headerEditor (string|Object|Function) -配置该列表头显示标题title +配置该列表头显示标题 title + ``` headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` @@ -230,4 +233,14 @@ ${prefix} columns (Array) 同上层的列配置数组,嵌套结构来描述列分组关系。 ${prefix} hideColumnsSubHeader(boolean) = false -是否隐藏子表头的header标题,默认不隐藏。 \ No newline at end of file +是否隐藏子表头的 header 标题,默认不隐藏。 + +${prefix} aggregation(Aggregation | CustomAggregation | Array) + +非必填。 + +数据聚合配置,对该列数据进行分析。 + +全局 option 也可以配置,对每一列都配置聚合规则。 + +可参考教程文档 diff --git a/docs/assets/option/zh/table/listTable.md b/docs/assets/option/zh/table/listTable.md index 7e8b7b643..eeea66592 100644 --- a/docs/assets/option/zh/table/listTable.md +++ b/docs/assets/option/zh/table/listTable.md @@ -36,7 +36,7 @@ 分页配置。 -基本表格和VTable数据分析透视表支持分页,透视组合图不支持分页。 +基本表格和 VTable 数据分析透视表支持分页,透视组合图不支持分页。 IPagination 的具体类型如下: @@ -44,13 +44,13 @@ IPagination 的具体类型如下: 数据总条数。 -非必传!透视表中这个字段VTable会自动补充,帮助用户获取到总共数据条数 +非必传!透视表中这个字段 VTable 会自动补充,帮助用户获取到总共数据条数 ### perPageCount (number) 每页显示数据条数。 -注意! 透视表中perPageCount会自动修正为指标数量的整数倍。 +注意! 透视表中 perPageCount 会自动修正为指标数量的整数倍。 ### currentPage (number) @@ -72,14 +72,17 @@ SortState { ## editor (string|Object|Function) 全局配置单元格编辑器 + ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts。 + +其中 IEditor 是@visactor/vtable-editors 中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts。 ${prefix} headerEditor (string|Object|Function) -全局配置表头显示标题title的编辑器 +全局配置表头显示标题 title 的编辑器 + ``` headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` @@ -95,13 +98,44 @@ headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI } ## hierarchyExpandLevel(number) -展示为树形结构时,默认展开层数。默认为1只显示根节点,配置为`Infinity`则全部展开。 - +展示为树形结构时,默认展开层数。默认为 1 只显示根节点,配置为`Infinity`则全部展开。 ## frozenColDragHeaderMode(string) = 'fixedFrozenCount' -拖拽表头移动位置 针对冻结部分的规则 默认为fixedFrozenCount +拖拽表头移动位置 针对冻结部分的规则 默认为 fixedFrozenCount - "disabled"(禁止调整冻结列位置):不允许其他列的表头移入冻结列,也不允许冻结列移出,冻结列保持不变。 - "adjustFrozenCount"(根据交互结果调整冻结数量):允许其他列的表头移入冻结列,及冻结列移出,并根据拖拽的动作调整冻结列的数量。当其他列的表头被拖拽进入冻结列位置时,冻结列数量增加;当其他列的表头被拖拽移出冻结列位置时,冻结列数量减少。 -- "fixedFrozenCount"(可调整冻结列,并维持冻结数量不变):允许自由拖拽其他列的表头移入或移出冻结列位置,同时保持冻结列的数量不变。 \ No newline at end of file +- "fixedFrozenCount"(可调整冻结列,并维持冻结数量不变):允许自由拖拽其他列的表头移入或移出冻结列位置,同时保持冻结列的数量不变。 + +## aggregation(Aggregation|CustomAggregation|Array|Function) + +数据聚合分析配置,全局配置每一列都将有聚合逻辑,也可以在列(columns)定义中配置,列中配置的优先级更高。 + +``` +aggregation?: + | Aggregation + | CustomAggregation + | (Aggregation | CustomAggregation)[] + | ((args: { + col: number; + field: string; + }) => Aggregation | CustomAggregation | (Aggregation | CustomAggregation)[] | null); +``` + +其中: + +``` +type Aggregation = { + aggregationType: AggregationType; + showOnTop?: boolean; + formatFun?: (value: number, col: number, row: number, table: BaseTableAPI) => string | number; +}; + +type CustomAggregation = { + aggregationType: AggregationType.CUSTOM; + aggregationFun: (values: any[], records: any[]) => any; + showOnTop?: boolean; + formatFun?: (value: number, col: number, row: number, table: BaseTableAPI) => string | number; +}; +``` diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index 6211d3737..e6cd02202 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -1,7 +1,5 @@ import * as sort from '../tools/sort'; import type { - Aggregation, - AggregationRule, CustomAggregation, DataSourceAPI, FieldAssessor, @@ -22,7 +20,6 @@ import { getValueByPath, isAllDigits } from '../tools/util'; import { calculateArrayDiff } from '../tools/diff-cell'; import { cloneDeep, isValid } from '@visactor/vutils'; import type { BaseTableAPI } from '../ts-types/base-table'; -import { TABLE_EVENT_TYPE } from '../core/TABLE_EVENT_TYPE'; import { RecordAggregator, type Aggregator, From b957c201257c4d7f035647b80d6b1d669e92aa44 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 19 Feb 2024 19:45:12 +0800 Subject: [PATCH 25/63] docs: add list table aggregation demos --- docs/assets/api/en/methods.md | 2 +- docs/assets/api/zh/methods.md | 2 +- .../list-table-aggregation-multiple.md | 244 +++++++++++ .../list-table-aggregation.md | 236 +++++++++++ .../list-table-data-filter.md | 235 +++++++++++ .../list-table-aggregation-multiple.md | 373 +++++++++-------- .../list-table-aggregation.md | 393 ++++++++--------- .../list-table-data-filter.md | 394 +++++++++--------- .../option/en/column/base-column-type.md | 2 +- docs/assets/option/en/table/listTable.md | 2 +- .../option/zh/column/base-column-type.md | 2 +- docs/assets/option/zh/table/listTable.md | 2 +- 12 files changed, 1301 insertions(+), 586 deletions(-) create mode 100644 docs/assets/demo/en/list-table-data-analysis/list-table-aggregation-multiple.md create mode 100644 docs/assets/demo/en/list-table-data-analysis/list-table-aggregation.md create mode 100644 docs/assets/demo/en/list-table-data-analysis/list-table-data-filter.md diff --git a/docs/assets/api/en/methods.md b/docs/assets/api/en/methods.md index 64d22e11a..692db9a51 100644 --- a/docs/assets/api/en/methods.md +++ b/docs/assets/api/en/methods.md @@ -946,4 +946,4 @@ Get the displayed row number range of the table body part ## getAggregateValuesByField(Function) -Get aggregate value +Get aggregation summary value diff --git a/docs/assets/api/zh/methods.md b/docs/assets/api/zh/methods.md index 72bc0a6e5..2bbf791be 100644 --- a/docs/assets/api/zh/methods.md +++ b/docs/assets/api/zh/methods.md @@ -943,4 +943,4 @@ use case: 点击图例项后 更新过滤规则 来更新图表 ## getAggregateValuesByField(Function) -获取聚合值 +获取聚合汇总的值 diff --git a/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation-multiple.md b/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation-multiple.md new file mode 100644 index 000000000..636017f85 --- /dev/null +++ b/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation-multiple.md @@ -0,0 +1,244 @@ +--- +category: examples +group: list-table-data-analysis +title: Set multiple aggregation and aggregation summary methods for the same column of data +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-multiple-aggregation.png +link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +option: ListTable-columns-text#aggregation(Aggregation%20%7C%20CustomAggregation%20%7C%20Array) +--- + +# Set multiple aggregation and aggregation summary methods for the same column of data + +Basic table aggregation calculation, each column can set the aggregation method, and supports summation, average, maximum and minimum, and custom function summary logic. The same column and multiple aggregation methods are set, and the results are displayed in multiple rows. + +And this sample data supports editing, and the values that need to be aggregated are automatically calculated after editing. + +## Key Configurations + +- `ListTable` +- `columns.aggregation` Configure aggregation calculations + +## Code demo + +```javascript livedemo template=vtable +const input_editor = new VTable_editors.InputEditor({}); +VTable.register.editor('input', input_editor); +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + salary: Math.round(Math.random() * 10000) + })); +}; +var tableInstance; + +const records = generatePersons(300); +const columns = [ + { + field: '', + title: '行号', + width: 80, + fieldFormat(data, col, row, table) { + return row - 1; + }, + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun() { + return '汇总:'; + } + } + }, + { + field: 'id', + title: 'ID', + width: '1%', + minWidth: 200, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'salary', + title: 'salary', + width: 100 + }, + { + field: 'salary', + title: 'salary', + width: 100, + aggregation: [ + { + aggregationType: VTable.TYPES.AggregationType.MAX, + // showOnTop: true, + formatFun(value) { + return '最高薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: VTable.TYPES.AggregationType.MIN, + showOnTop: true, + formatFun(value) { + return '最低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: VTable.TYPES.AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ] + } +]; +const option = { + container: document.getElementById(CONTAINER_ID), + records, + // dataConfig: { + // filterRules: [ + // { + // filterFunc: (record: Record) => { + // return record.id % 2 === 0; + // } + // } + // ] + // }, + columns, + tooltip: { + isShowOverflowTextTooltip: true + }, + frozenColCount: 1, + bottomFrozenRowCount: 2, + rightFrozenColCount: 1, + overscrollBehavior: 'none', + autoWrapText: true, + widthMode: 'autoWidth', + heightMode: 'autoHeight', + dragHeaderMode: 'all', + keyboardOptions: { + pasteValueToCell: true + }, + eventOptions: { + preventDefaultContextMenu: false + }, + pagination: { + perPageCount: 100, + currentPage: 0 + }, + theme: VTable.themes.DEFAULT.extends({ + bottomFrozenStyle: { + bgColor: '#ECF1F5', + borderLineWidth: [6, 0, 1, 0], + borderColor: ['gray'] + } + }), + editor: 'input', + headerEditor: 'input', + aggregation(args) { + if (args.col === 1) { + return [ + { + aggregationType: VTable.TYPES.AggregationType.MAX, + formatFun(value) { + return '最大ID:' + Math.round(value) + '号'; + } + }, + { + aggregationType: VTable.TYPES.AggregationType.MIN, + showOnTop: false, + formatFun(value, col, row, table) { + return '最小ID:' + Math.round(value) + '号'; + } + } + ]; + } + if (args.field === 'salary') { + return [ + { + aggregationType: VTable.TYPES.AggregationType.MIN, + formatFun(value) { + return '最低低低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: VTable.TYPES.AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均平均平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ]; + } + return null; + } + // transpose: true + // widthMode: 'adaptive' +}; +tableInstance = new VTable.ListTable(option); +// tableInstance.updateFilterRules([ +// { +// filterKey: 'sex', +// filteredValues: ['boy'] +// } +// ]); +window.tableInstance = tableInstance; +tableInstance.on('change_cell_value', arg => { + console.log(arg); +}); +``` diff --git a/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation.md b/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation.md new file mode 100644 index 000000000..b7d6377f0 --- /dev/null +++ b/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation.md @@ -0,0 +1,236 @@ +--- +category: examples +group: list-table-data-analysis +title: List Table data aggregation summary +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-aggregation.png +link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +option: ListTable-columns-text#aggregation(Aggregation%20%7C%20CustomAggregation%20%7C%20Array) +--- + +# List table aggregation summary + +Basic table aggregation calculation, each column can set the aggregation method, and supports summation, average, maximum and minimum, and custom function summary logic. + +## Key Configurations + +- `ListTable` +- `columns.aggregation` Configure aggregation calculations + +## Code demo + +```javascript livedemo template=vtable +var tableInstance; +VTable.register.icon('filter', { + name: 'filter', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' +}); + +VTable.register.icon('filtered', { + name: 'filtered', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' +}); +fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') + .then(res => res.json()) + .then(data => { + const columns = [ + { + field: 'athlete', + title: 'athlete', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun(value) { + return 'Total:'; + } + } + }, + { + field: 'age', + title: 'age', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.AVG, + formatFun(value) { + return Math.round(value) + '(Avg)'; + } + } + }, + { + field: 'country', + title: 'country', + headerIcon: 'filter', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.CUSTOM, + aggregationFun(values, records) { + // 使用 reduce() 方法统计金牌数 + const goldMedalCountByCountry = records.reduce((acc, data) => { + const country = data.country; + const gold = data.gold; + + if (acc[country]) { + acc[country] += gold; + } else { + acc[country] = gold; + } + return acc; + }, {}); + + // 找出金牌数最多的国家 + let maxGoldMedals = 0; + let countryWithMaxGoldMedals = ''; + + for (const country in goldMedalCountByCountry) { + if (goldMedalCountByCountry[country] > maxGoldMedals) { + maxGoldMedals = goldMedalCountByCountry[country]; + countryWithMaxGoldMedals = country; + } + } + return { + country: countryWithMaxGoldMedals, + gold: maxGoldMedals + }; + }, + formatFun(value) { + return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; + } + } + }, + { field: 'year', title: 'year', headerIcon: 'filter' }, + { field: 'sport', title: 'sport', headerIcon: 'filter' }, + { + field: 'gold', + title: 'gold', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'silver', + title: 'silver', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'bronze', + title: 'bronze', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'total', + title: 'total', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + } + ]; + const option = { + columns, + records: data, + autoWrapText: true, + heightMode: 'autoHeight', + widthMode: 'autoWidth', + bottomFrozenRowCount: 1, + theme: VTable.themes.ARCO.extends({ + bottomFrozenStyle: { + fontFamily: 'PingFang SC', + fontWeight: 500 + } + }) + }; + const t0 = window.performance.now(); + tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + window.tableInstance = tableInstance; + const filterListValues = { + country: ['all', 'China', 'United States', 'Australia'], + year: ['all', '2004', '2008', '2012', '2016', '2020'], + sport: ['all', 'Swimming', 'Cycling', 'Biathlon', 'Short-Track Speed Skating', 'Nordic Combined'] + }; + let filterListSelectedValues = ''; + let lastFilterField; + tableInstance.on('icon_click', args => { + const { col, row, name } = args; + if (name === 'filter') { + const field = tableInstance.getHeaderField(col, row); + if (select && lastFilterField === field) { + removeFilterElement(); + lastFilterField = null; + } else if (!select || lastFilterField !== field) { + const rect = tableInstance.getCellRelativeRect(col, row); + createFilterElement(filterListValues[field], filterListSelectedValues, field, rect); + lastFilterField = field; + } + } + }); + + let filterContainer = tableInstance.getElement(); + let select; + function createFilterElement(values, curValue, field, positonRect) { + // create select tag + select = document.createElement('select'); + select.setAttribute('type', 'text'); + select.style.position = 'absolute'; + select.style.padding = '4px'; + select.style.width = '100%'; + select.style.boxSizing = 'border-box'; + + // create option tags + let opsStr = ''; + values.forEach(item => { + opsStr += + item === curValue + ? `` + : ``; + }); + select.innerHTML = opsStr; + + filterContainer.appendChild(select); + + select.style.top = positonRect.top + positonRect.height + 'px'; + select.style.left = positonRect.left + 'px'; + select.style.width = positonRect.width + 'px'; + select.style.height = positonRect.height + 'px'; + select.addEventListener('change', () => { + filterListSelectedValues = select.value; + tableInstance.updateFilterRules([ + { + filterKey: field, + filteredValues: select.value + } + ]); + removeFilterElement(); + }); + } + function removeFilterElement() { + filterContainer.removeChild(select); + select.removeEventListener('change', () => { + // this.successCallback(); + }); + select = null; + } + }); +``` diff --git a/docs/assets/demo/en/list-table-data-analysis/list-table-data-filter.md b/docs/assets/demo/en/list-table-data-analysis/list-table-data-filter.md new file mode 100644 index 000000000..872b0df2e --- /dev/null +++ b/docs/assets/demo/en/list-table-data-analysis/list-table-data-filter.md @@ -0,0 +1,235 @@ +--- +category: examples +group: list-table-data-analysis +title: List table data filtering +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-filter.gif +link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +--- + +# List table data filtering + +The basic table sets filtering through the interface updateFilterRules, supporting value filtering and function filtering. + +## Key Configurations + +- `ListTable` +- `updateFilterRules` sets or updates filtering data rules + +## Code demo + +```javascript livedemo template=vtable +var tableInstance; +VTable.register.icon('filter', { + name: 'filter', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' +}); + +VTable.register.icon('filtered', { + name: 'filtered', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' +}); +fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') + .then(res => res.json()) + .then(data => { + const columns = [ + { + field: 'athlete', + title: 'athlete', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun(value) { + return 'Total:'; + } + } + }, + { + field: 'age', + title: 'age', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.AVG, + formatFun(value) { + return Math.round(value) + '(Avg)'; + } + } + }, + { + field: 'country', + title: 'country', + headerIcon: 'filter', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.CUSTOM, + aggregationFun(values, records) { + // 使用 reduce() 方法统计金牌数 + const goldMedalCountByCountry = records.reduce((acc, data) => { + const country = data.country; + const gold = data.gold; + + if (acc[country]) { + acc[country] += gold; + } else { + acc[country] = gold; + } + return acc; + }, {}); + + // 找出金牌数最多的国家 + let maxGoldMedals = 0; + let countryWithMaxGoldMedals = ''; + + for (const country in goldMedalCountByCountry) { + if (goldMedalCountByCountry[country] > maxGoldMedals) { + maxGoldMedals = goldMedalCountByCountry[country]; + countryWithMaxGoldMedals = country; + } + } + return { + country: countryWithMaxGoldMedals, + gold: maxGoldMedals + }; + }, + formatFun(value) { + return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; + } + } + }, + { field: 'year', title: 'year', headerIcon: 'filter' }, + { field: 'sport', title: 'sport', headerIcon: 'filter' }, + { + field: 'gold', + title: 'gold', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'silver', + title: 'silver', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'bronze', + title: 'bronze', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + }, + { + field: 'total', + title: 'total', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; + } + } + } + ]; + const option = { + columns, + records: data, + autoWrapText: true, + heightMode: 'autoHeight', + widthMode: 'autoWidth', + bottomFrozenRowCount: 1, + theme: VTable.themes.ARCO.extends({ + bottomFrozenStyle: { + fontFamily: 'PingFang SC', + fontWeight: 500 + } + }) + }; + const t0 = window.performance.now(); + tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + window.tableInstance = tableInstance; + const filterListValues = { + country: ['all', 'China', 'United States', 'Australia'], + year: ['all', '2004', '2008', '2012', '2016', '2020'], + sport: ['all', 'Swimming', 'Cycling', 'Biathlon', 'Short-Track Speed Skating', 'Nordic Combined'] + }; + let filterListSelectedValues = ''; + let lastFilterField; + tableInstance.on('icon_click', args => { + const { col, row, name } = args; + if (name === 'filter') { + const field = tableInstance.getHeaderField(col, row); + if (select && lastFilterField === field) { + removeFilterElement(); + lastFilterField = null; + } else if (!select || lastFilterField !== field) { + const rect = tableInstance.getCellRelativeRect(col, row); + createFilterElement(filterListValues[field], filterListSelectedValues, field, rect); + lastFilterField = field; + } + } + }); + + let filterContainer = tableInstance.getElement(); + let select; + function createFilterElement(values, curValue, field, positonRect) { + // create select tag + select = document.createElement('select'); + select.setAttribute('type', 'text'); + select.style.position = 'absolute'; + select.style.padding = '4px'; + select.style.width = '100%'; + select.style.boxSizing = 'border-box'; + + // create option tags + let opsStr = ''; + values.forEach(item => { + opsStr += + item === curValue + ? `` + : ``; + }); + select.innerHTML = opsStr; + + filterContainer.appendChild(select); + + select.style.top = positonRect.top + positonRect.height + 'px'; + select.style.left = positonRect.left + 'px'; + select.style.width = positonRect.width + 'px'; + select.style.height = positonRect.height + 'px'; + select.addEventListener('change', () => { + filterListSelectedValues = select.value; + tableInstance.updateFilterRules([ + { + filterKey: field, + filteredValues: select.value + } + ]); + removeFilterElement(); + }); + } + function removeFilterElement() { + filterContainer.removeChild(select); + select.removeEventListener('change', () => { + // this.successCallback(); + }); + select = null; + } + }); +``` diff --git a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md index 0f2799aa2..2cfd7ce6a 100644 --- a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md +++ b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md @@ -2,9 +2,9 @@ category: examples group: list-table-data-analysis title: 同一列数据设置多种聚合汇总方式 -cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-analysis-aggregation.png -link: '../guide/table_type/Pivot_table/pivot_table_dataAnalysis' -option: PivotTable#dataConfig.aggregationRules +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-multiple-aggregation.png +link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +option: ListTable-columns-text#aggregation(Aggregation%20%7C%20CustomAggregation%20%7C%20Array) --- # 同一列数据设置多种聚合汇总方式 @@ -17,10 +17,10 @@ option: PivotTable#dataConfig.aggregationRules - `ListTable` - `columns.aggregation` 配置聚合计算 + ## 代码演示 ```javascript livedemo template=vtable - const input_editor = new VTable_editors.InputEditor({}); VTable.register.editor('input', input_editor); const generatePersons = count => { @@ -39,207 +39,206 @@ const generatePersons = count => { }; var tableInstance; - const records = generatePersons(300); - const columns = [ - { - field: '', - title: '行号', - width: 80, - fieldFormat(data, col, row, table) { - return row - 1; +const records = generatePersons(300); +const columns = [ + { + field: '', + title: '行号', + width: 80, + fieldFormat(data, col, row, table) { + return row - 1; + }, + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun() { + return '汇总:'; + } + } + }, + { + field: 'id', + title: 'ID', + width: '1%', + minWidth: 200, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 }, - aggregation: { - aggregationType: VTable.TYPES.AggregationType.NONE, - formatFun() { - return '汇总:'; - } + { + field: 'name', + title: 'Last Name', + width: 200 } - }, - { - field: 'id', - title: 'ID', - width: '1%', - minWidth: 200, - sort: true - }, - { - field: 'email1', - title: 'email', - width: 200, - sort: true - }, - { - title: 'full name', - columns: [ - { - field: 'name', - title: 'First Name', - width: 200 - }, - { - field: 'name', - title: 'Last Name', - width: 200 + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + }, + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'salary', + title: 'salary', + width: 100 + }, + { + field: 'salary', + title: 'salary', + width: 100, + aggregation: [ + { + aggregationType: VTable.TYPES.AggregationType.MAX, + // showOnTop: true, + formatFun(value) { + return '最高薪资:' + Math.round(value) + '元'; } - ] - }, - { - field: 'date1', - title: 'birthday', - width: 200 - }, - { - field: 'sex', - title: 'sex', - width: 100 - }, - { - field: 'tel', - title: 'telephone', - width: 150 - }, - { - field: 'work', - title: 'job', - width: 200 - }, - { - field: 'city', - title: 'city', - width: 150 - }, - { - field: 'date1', - title: 'birthday', - width: 200 - }, - { - field: 'salary', - title: 'salary', - width: 100 - }, - { - field: 'salary', - title: 'salary', - width: 100, - aggregation: [ + }, + { + aggregationType: VTable.TYPES.AggregationType.MIN, + showOnTop: true, + formatFun(value) { + return '最低薪资:' + Math.round(value) + '元'; + } + }, + { + aggregationType: VTable.TYPES.AggregationType.AVG, + showOnTop: false, + formatFun(value, col, row, table) { + return '平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + } + } + ] + } +]; +const option = { + container: document.getElementById(CONTAINER_ID), + records, + // dataConfig: { + // filterRules: [ + // { + // filterFunc: (record: Record) => { + // return record.id % 2 === 0; + // } + // } + // ] + // }, + columns, + tooltip: { + isShowOverflowTextTooltip: true + }, + frozenColCount: 1, + bottomFrozenRowCount: 2, + rightFrozenColCount: 1, + overscrollBehavior: 'none', + autoWrapText: true, + widthMode: 'autoWidth', + heightMode: 'autoHeight', + dragHeaderMode: 'all', + keyboardOptions: { + pasteValueToCell: true + }, + eventOptions: { + preventDefaultContextMenu: false + }, + pagination: { + perPageCount: 100, + currentPage: 0 + }, + theme: VTable.themes.DEFAULT.extends({ + bottomFrozenStyle: { + bgColor: '#ECF1F5', + borderLineWidth: [6, 0, 1, 0], + borderColor: ['gray'] + } + }), + editor: 'input', + headerEditor: 'input', + aggregation(args) { + if (args.col === 1) { + return [ { aggregationType: VTable.TYPES.AggregationType.MAX, - // showOnTop: true, formatFun(value) { - return '最高薪资:' + Math.round(value) + '元'; + return '最大ID:' + Math.round(value) + '号'; } }, { aggregationType: VTable.TYPES.AggregationType.MIN, - showOnTop: true, + showOnTop: false, + formatFun(value, col, row, table) { + return '最小ID:' + Math.round(value) + '号'; + } + } + ]; + } + if (args.field === 'salary') { + return [ + { + aggregationType: VTable.TYPES.AggregationType.MIN, formatFun(value) { - return '最低薪资:' + Math.round(value) + '元'; + return '最低低低薪资:' + Math.round(value) + '元'; } }, { aggregationType: VTable.TYPES.AggregationType.AVG, showOnTop: false, formatFun(value, col, row, table) { - return '平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; + return '平均平均平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; } } - ] - } - ]; - const option = { - container: document.getElementById(CONTAINER_ID), - records, - // dataConfig: { - // filterRules: [ - // { - // filterFunc: (record: Record) => { - // return record.id % 2 === 0; - // } - // } - // ] - // }, - columns, - tooltip: { - isShowOverflowTextTooltip: true - }, - frozenColCount: 1, - bottomFrozenRowCount: 2, - rightFrozenColCount: 1, - overscrollBehavior: 'none', - autoWrapText: true, - widthMode: 'autoWidth', - heightMode: 'autoHeight', - dragHeaderMode: 'all', - keyboardOptions: { - pasteValueToCell: true - }, - eventOptions: { - preventDefaultContextMenu: false - }, - pagination: { - perPageCount: 100, - currentPage: 0 - }, - theme: VTable.themes.DEFAULT.extends({ - bottomFrozenStyle: { - bgColor: '#ECF1F5', - borderLineWidth: [6, 0, 1, 0], - borderColor: ['gray'] - } - }), - editor: 'input', - headerEditor: 'input', - aggregation(args) { - if (args.col === 1) { - return [ - { - aggregationType: VTable.TYPES.AggregationType.MAX, - formatFun(value) { - return '最大ID:' + Math.round(value) + '号'; - } - }, - { - aggregationType: VTable.TYPES.AggregationType.MIN, - showOnTop: false, - formatFun(value, col, row, table) { - return '最小ID:' + Math.round(value) + '号'; - } - } - ]; - } - if (args.field === 'salary') { - return [ - { - aggregationType: VTable.TYPES.AggregationType.MIN, - formatFun(value) { - return '最低低低薪资:' + Math.round(value) + '元'; - } - }, - { - aggregationType: VTable.TYPES.AggregationType.AVG, - showOnTop: false, - formatFun(value, col, row, table) { - return '平均平均平均:' + Math.round(value) + '元 (共计' + table.recordsCount + '条数据)'; - } - } - ]; - } - return null; + ]; } - // transpose: true - // widthMode: 'adaptive' - }; - tableInstance = new VTable.ListTable(option); - // tableInstance.updateFilterRules([ - // { - // filterKey: 'sex', - // filteredValues: ['boy'] - // } - // ]); - window.tableInstance = tableInstance; - tableInstance.on('change_cell_value', arg => { - console.log(arg); - }); - + return null; + } + // transpose: true + // widthMode: 'adaptive' +}; +tableInstance = new VTable.ListTable(option); +// tableInstance.updateFilterRules([ +// { +// filterKey: 'sex', +// filteredValues: ['boy'] +// } +// ]); +window.tableInstance = tableInstance; +tableInstance.on('change_cell_value', arg => { + console.log(arg); +}); ``` diff --git a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md index cb7672a13..b60febfa7 100644 --- a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md +++ b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md @@ -2,9 +2,9 @@ category: examples group: list-table-data-analysis title: 基本表格数据聚合分析 -cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-analysis-aggregation.png -link: '../guide/table_type/Pivot_table/pivot_table_dataAnalysis' -option: PivotTable#dataConfig.aggregationRules +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-aggregation.png +link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +option: ListTable-columns-text#aggregation(Aggregation%20%7C%20CustomAggregation%20%7C%20Array) --- # 数据聚合汇总 @@ -15,221 +15,222 @@ option: PivotTable#dataConfig.aggregationRules - `ListTable` - `columns.aggregation` 配置聚合计算 + ## 代码演示 ```javascript livedemo template=vtable var tableInstance; VTable.register.icon('filter', { - name: 'filter', - type: 'svg', - width: 20, - height: 20, - marginRight: 6, - positionType: VTable.TYPES.IconPosition.right, - // interactive: true, - svg: '' - }); + name: 'filter', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' +}); - VTable.register.icon('filtered', { - name: 'filtered', - type: 'svg', - width: 20, - height: 20, - marginRight: 6, - positionType: VTable.TYPES.IconPosition.right, - // interactive: true, - svg: '' - }); - fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') - .then(res => res.json()) - .then(data => { - const columns = [ - { - field: 'athlete', - title: 'athlete', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.NONE, - formatFun(value) { - return 'Total:'; - } +VTable.register.icon('filtered', { + name: 'filtered', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' +}); +fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') + .then(res => res.json()) + .then(data => { + const columns = [ + { + field: 'athlete', + title: 'athlete', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun(value) { + return 'Total:'; } - }, - { - field: 'age', - title: 'age', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.AVG, - formatFun(value) { - return Math.round(value) + '(Avg)'; - } + } + }, + { + field: 'age', + title: 'age', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.AVG, + formatFun(value) { + return Math.round(value) + '(Avg)'; } - }, - { - field: 'country', - title: 'country', - headerIcon: 'filter', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.CUSTOM, - aggregationFun(values, records) { - // 使用 reduce() 方法统计金牌数 - const goldMedalCountByCountry = records.reduce((acc, data) => { - const country = data.country; - const gold = data.gold; + } + }, + { + field: 'country', + title: 'country', + headerIcon: 'filter', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.CUSTOM, + aggregationFun(values, records) { + // 使用 reduce() 方法统计金牌数 + const goldMedalCountByCountry = records.reduce((acc, data) => { + const country = data.country; + const gold = data.gold; - if (acc[country]) { - acc[country] += gold; - } else { - acc[country] = gold; - } - return acc; - }, {}); + if (acc[country]) { + acc[country] += gold; + } else { + acc[country] = gold; + } + return acc; + }, {}); - // 找出金牌数最多的国家 - let maxGoldMedals = 0; - let countryWithMaxGoldMedals = ''; + // 找出金牌数最多的国家 + let maxGoldMedals = 0; + let countryWithMaxGoldMedals = ''; - for (const country in goldMedalCountByCountry) { - if (goldMedalCountByCountry[country] > maxGoldMedals) { - maxGoldMedals = goldMedalCountByCountry[country]; - countryWithMaxGoldMedals = country; - } + for (const country in goldMedalCountByCountry) { + if (goldMedalCountByCountry[country] > maxGoldMedals) { + maxGoldMedals = goldMedalCountByCountry[country]; + countryWithMaxGoldMedals = country; } - return { - country: countryWithMaxGoldMedals, - gold: maxGoldMedals - }; - }, - formatFun(value) { - return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; } + return { + country: countryWithMaxGoldMedals, + gold: maxGoldMedals + }; + }, + formatFun(value) { + return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; } - }, - { field: 'year', title: 'year', headerIcon: 'filter' }, - { field: 'sport', title: 'sport', headerIcon: 'filter' }, - { - field: 'gold', - title: 'gold', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.SUM, - formatFun(value) { - return Math.round(value) + '(Sum)'; - } - } - }, - { - field: 'silver', - title: 'silver', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.SUM, - formatFun(value) { - return Math.round(value) + '(Sum)'; - } - } - }, - { - field: 'bronze', - title: 'bronze', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.SUM, - formatFun(value) { - return Math.round(value) + '(Sum)'; - } + } + }, + { field: 'year', title: 'year', headerIcon: 'filter' }, + { field: 'sport', title: 'sport', headerIcon: 'filter' }, + { + field: 'gold', + title: 'gold', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; } - }, - { - field: 'total', - title: 'total', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.SUM, - formatFun(value) { - return Math.round(value) + '(Sum)'; - } + } + }, + { + field: 'silver', + title: 'silver', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; } } - ]; - const option = { - columns, - records: data, - autoWrapText: true, - heightMode: 'autoHeight', - widthMode: 'autoWidth', - bottomFrozenRowCount: 1, - theme:VTable.themes.ARCO.extends({ - bottomFrozenStyle:{ - fontFamily: 'PingFang SC', - fontWeight: 500, + }, + { + field: 'bronze', + title: 'bronze', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; } - }) - }; - const t0 = window.performance.now(); - tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); - window.tableInstance = tableInstance; - const filterListValues = { - country: ['all', 'China', 'United States', 'Australia'], - year: ['all', '2004', '2008', '2012', '2016', '2020'], - sport: ['all', 'Swimming', 'Cycling', 'Biathlon', 'Short-Track Speed Skating', 'Nordic Combined'] - }; - let filterListSelectedValues = ''; - let lastFilterField; - tableInstance.on('icon_click', args => { - const { col, row, name } = args; - if (name === 'filter') { - const field = tableInstance.getHeaderField(col, row); - if (select && lastFilterField === field) { - removeFilterElement(); - lastFilterField = null; - } else if (!select || lastFilterField !== field) { - const rect = tableInstance.getCellRelativeRect(col, row); - createFilterElement(filterListValues[field], filterListSelectedValues, field, rect); - lastFilterField = field; + } + }, + { + field: 'total', + title: 'total', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; } } - }); + } + ]; + const option = { + columns, + records: data, + autoWrapText: true, + heightMode: 'autoHeight', + widthMode: 'autoWidth', + bottomFrozenRowCount: 1, + theme: VTable.themes.ARCO.extends({ + bottomFrozenStyle: { + fontFamily: 'PingFang SC', + fontWeight: 500 + } + }) + }; + const t0 = window.performance.now(); + tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + window.tableInstance = tableInstance; + const filterListValues = { + country: ['all', 'China', 'United States', 'Australia'], + year: ['all', '2004', '2008', '2012', '2016', '2020'], + sport: ['all', 'Swimming', 'Cycling', 'Biathlon', 'Short-Track Speed Skating', 'Nordic Combined'] + }; + let filterListSelectedValues = ''; + let lastFilterField; + tableInstance.on('icon_click', args => { + const { col, row, name } = args; + if (name === 'filter') { + const field = tableInstance.getHeaderField(col, row); + if (select && lastFilterField === field) { + removeFilterElement(); + lastFilterField = null; + } else if (!select || lastFilterField !== field) { + const rect = tableInstance.getCellRelativeRect(col, row); + createFilterElement(filterListValues[field], filterListSelectedValues, field, rect); + lastFilterField = field; + } + } + }); - let filterContainer = tableInstance.getElement(); - let select; - function createFilterElement(values, curValue, field, positonRect) { - // create select tag - select = document.createElement('select'); - select.setAttribute('type', 'text'); - select.style.position = 'absolute'; - select.style.padding = '4px'; - select.style.width = '100%'; - select.style.boxSizing = 'border-box'; + let filterContainer = tableInstance.getElement(); + let select; + function createFilterElement(values, curValue, field, positonRect) { + // create select tag + select = document.createElement('select'); + select.setAttribute('type', 'text'); + select.style.position = 'absolute'; + select.style.padding = '4px'; + select.style.width = '100%'; + select.style.boxSizing = 'border-box'; - // create option tags - let opsStr = ''; - values.forEach(item => { - opsStr += - item === curValue - ? `` - : ``; - }); - select.innerHTML = opsStr; + // create option tags + let opsStr = ''; + values.forEach(item => { + opsStr += + item === curValue + ? `` + : ``; + }); + select.innerHTML = opsStr; - filterContainer.appendChild(select); + filterContainer.appendChild(select); - select.style.top = positonRect.top + positonRect.height + 'px'; - select.style.left = positonRect.left + 'px'; - select.style.width = positonRect.width + 'px'; - select.style.height = positonRect.height + 'px'; - select.addEventListener('change', () => { - filterListSelectedValues = select.value; - tableInstance.updateFilterRules([ - { - filterKey: field, - filteredValues: select.value - } - ]); - removeFilterElement(); - }); - } - function removeFilterElement() { - filterContainer.removeChild(select); - select.removeEventListener('change', () => { - // this.successCallback(); - }); - select = null; - } - }); + select.style.top = positonRect.top + positonRect.height + 'px'; + select.style.left = positonRect.left + 'px'; + select.style.width = positonRect.width + 'px'; + select.style.height = positonRect.height + 'px'; + select.addEventListener('change', () => { + filterListSelectedValues = select.value; + tableInstance.updateFilterRules([ + { + filterKey: field, + filteredValues: select.value + } + ]); + removeFilterElement(); + }); + } + function removeFilterElement() { + filterContainer.removeChild(select); + select.removeEventListener('change', () => { + // this.successCallback(); + }); + select = null; + } + }); ``` diff --git a/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md b/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md index 51fe54da0..5caba692b 100644 --- a/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md +++ b/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md @@ -2,234 +2,234 @@ category: examples group: list-table-data-analysis title: 基本表格数据过滤 -cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-analysis-aggregation.png -link: '../guide/table_type/Pivot_table/pivot_table_dataAnalysis' -option: PivotTable#dataConfig.aggregationRules +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-filter.gif +link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' --- # 基本表格数据过滤 -基本表格通过接口updateFilterRules来设置过滤,支持值过滤和函数过滤。 +基本表格通过接口 updateFilterRules 来设置过滤,支持值过滤和函数过滤。 ## 关键配置 - `ListTable` - `updateFilterRules` 设置或者更新过滤数据规则 + ## 代码演示 ```javascript livedemo template=vtable var tableInstance; VTable.register.icon('filter', { - name: 'filter', - type: 'svg', - width: 20, - height: 20, - marginRight: 6, - positionType: VTable.TYPES.IconPosition.right, - // interactive: true, - svg: '' - }); + name: 'filter', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' +}); - VTable.register.icon('filtered', { - name: 'filtered', - type: 'svg', - width: 20, - height: 20, - marginRight: 6, - positionType: VTable.TYPES.IconPosition.right, - // interactive: true, - svg: '' - }); - fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') - .then(res => res.json()) - .then(data => { - const columns = [ - { - field: 'athlete', - title: 'athlete', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.NONE, - formatFun(value) { - return 'Total:'; - } +VTable.register.icon('filtered', { + name: 'filtered', + type: 'svg', + width: 20, + height: 20, + marginRight: 6, + positionType: VTable.TYPES.IconPosition.right, + // interactive: true, + svg: '' +}); +fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/olympic-winners.json') + .then(res => res.json()) + .then(data => { + const columns = [ + { + field: 'athlete', + title: 'athlete', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.NONE, + formatFun(value) { + return 'Total:'; } - }, - { - field: 'age', - title: 'age', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.AVG, - formatFun(value) { - return Math.round(value) + '(Avg)'; - } + } + }, + { + field: 'age', + title: 'age', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.AVG, + formatFun(value) { + return Math.round(value) + '(Avg)'; } - }, - { - field: 'country', - title: 'country', - headerIcon: 'filter', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.CUSTOM, - aggregationFun(values, records) { - // 使用 reduce() 方法统计金牌数 - const goldMedalCountByCountry = records.reduce((acc, data) => { - const country = data.country; - const gold = data.gold; + } + }, + { + field: 'country', + title: 'country', + headerIcon: 'filter', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.CUSTOM, + aggregationFun(values, records) { + // 使用 reduce() 方法统计金牌数 + const goldMedalCountByCountry = records.reduce((acc, data) => { + const country = data.country; + const gold = data.gold; - if (acc[country]) { - acc[country] += gold; - } else { - acc[country] = gold; - } - return acc; - }, {}); + if (acc[country]) { + acc[country] += gold; + } else { + acc[country] = gold; + } + return acc; + }, {}); - // 找出金牌数最多的国家 - let maxGoldMedals = 0; - let countryWithMaxGoldMedals = ''; + // 找出金牌数最多的国家 + let maxGoldMedals = 0; + let countryWithMaxGoldMedals = ''; - for (const country in goldMedalCountByCountry) { - if (goldMedalCountByCountry[country] > maxGoldMedals) { - maxGoldMedals = goldMedalCountByCountry[country]; - countryWithMaxGoldMedals = country; - } + for (const country in goldMedalCountByCountry) { + if (goldMedalCountByCountry[country] > maxGoldMedals) { + maxGoldMedals = goldMedalCountByCountry[country]; + countryWithMaxGoldMedals = country; } - return { - country: countryWithMaxGoldMedals, - gold: maxGoldMedals - }; - }, - formatFun(value) { - return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; } + return { + country: countryWithMaxGoldMedals, + gold: maxGoldMedals + }; + }, + formatFun(value) { + return `Top country in gold medals: ${value.country},\nwith ${value.gold} gold medals`; } - }, - { field: 'year', title: 'year', headerIcon: 'filter' }, - { field: 'sport', title: 'sport', headerIcon: 'filter' }, - { - field: 'gold', - title: 'gold', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.SUM, - formatFun(value) { - return Math.round(value) + '(Sum)'; - } - } - }, - { - field: 'silver', - title: 'silver', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.SUM, - formatFun(value) { - return Math.round(value) + '(Sum)'; - } - } - }, - { - field: 'bronze', - title: 'bronze', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.SUM, - formatFun(value) { - return Math.round(value) + '(Sum)'; - } + } + }, + { field: 'year', title: 'year', headerIcon: 'filter' }, + { field: 'sport', title: 'sport', headerIcon: 'filter' }, + { + field: 'gold', + title: 'gold', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; } - }, - { - field: 'total', - title: 'total', - aggregation: { - aggregationType: VTable.TYPES.AggregationType.SUM, - formatFun(value) { - return Math.round(value) + '(Sum)'; - } + } + }, + { + field: 'silver', + title: 'silver', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; } } - ]; - const option = { - columns, - records: data, - autoWrapText: true, - heightMode: 'autoHeight', - widthMode: 'autoWidth', - bottomFrozenRowCount: 1, - theme:VTable.themes.ARCO.extends({ - bottomFrozenStyle:{ - fontFamily: 'PingFang SC', - fontWeight: 500, + }, + { + field: 'bronze', + title: 'bronze', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; } - }) - }; - const t0 = window.performance.now(); - tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); - window.tableInstance = tableInstance; - const filterListValues = { - country: ['all', 'China', 'United States', 'Australia'], - year: ['all', '2004', '2008', '2012', '2016', '2020'], - sport: ['all', 'Swimming', 'Cycling', 'Biathlon', 'Short-Track Speed Skating', 'Nordic Combined'] - }; - let filterListSelectedValues = ''; - let lastFilterField; - tableInstance.on('icon_click', args => { - const { col, row, name } = args; - if (name === 'filter') { - const field = tableInstance.getHeaderField(col, row); - if (select && lastFilterField === field) { - removeFilterElement(); - lastFilterField = null; - } else if (!select || lastFilterField !== field) { - const rect = tableInstance.getCellRelativeRect(col, row); - createFilterElement(filterListValues[field], filterListSelectedValues, field, rect); - lastFilterField = field; + } + }, + { + field: 'total', + title: 'total', + aggregation: { + aggregationType: VTable.TYPES.AggregationType.SUM, + formatFun(value) { + return Math.round(value) + '(Sum)'; } } - }); + } + ]; + const option = { + columns, + records: data, + autoWrapText: true, + heightMode: 'autoHeight', + widthMode: 'autoWidth', + bottomFrozenRowCount: 1, + theme: VTable.themes.ARCO.extends({ + bottomFrozenStyle: { + fontFamily: 'PingFang SC', + fontWeight: 500 + } + }) + }; + const t0 = window.performance.now(); + tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + window.tableInstance = tableInstance; + const filterListValues = { + country: ['all', 'China', 'United States', 'Australia'], + year: ['all', '2004', '2008', '2012', '2016', '2020'], + sport: ['all', 'Swimming', 'Cycling', 'Biathlon', 'Short-Track Speed Skating', 'Nordic Combined'] + }; + let filterListSelectedValues = ''; + let lastFilterField; + tableInstance.on('icon_click', args => { + const { col, row, name } = args; + if (name === 'filter') { + const field = tableInstance.getHeaderField(col, row); + if (select && lastFilterField === field) { + removeFilterElement(); + lastFilterField = null; + } else if (!select || lastFilterField !== field) { + const rect = tableInstance.getCellRelativeRect(col, row); + createFilterElement(filterListValues[field], filterListSelectedValues, field, rect); + lastFilterField = field; + } + } + }); - let filterContainer = tableInstance.getElement(); - let select; - function createFilterElement(values, curValue, field, positonRect) { - // create select tag - select = document.createElement('select'); - select.setAttribute('type', 'text'); - select.style.position = 'absolute'; - select.style.padding = '4px'; - select.style.width = '100%'; - select.style.boxSizing = 'border-box'; + let filterContainer = tableInstance.getElement(); + let select; + function createFilterElement(values, curValue, field, positonRect) { + // create select tag + select = document.createElement('select'); + select.setAttribute('type', 'text'); + select.style.position = 'absolute'; + select.style.padding = '4px'; + select.style.width = '100%'; + select.style.boxSizing = 'border-box'; - // create option tags - let opsStr = ''; - values.forEach(item => { - opsStr += - item === curValue - ? `` - : ``; - }); - select.innerHTML = opsStr; + // create option tags + let opsStr = ''; + values.forEach(item => { + opsStr += + item === curValue + ? `` + : ``; + }); + select.innerHTML = opsStr; - filterContainer.appendChild(select); + filterContainer.appendChild(select); - select.style.top = positonRect.top + positonRect.height + 'px'; - select.style.left = positonRect.left + 'px'; - select.style.width = positonRect.width + 'px'; - select.style.height = positonRect.height + 'px'; - select.addEventListener('change', () => { - filterListSelectedValues = select.value; - tableInstance.updateFilterRules([ - { - filterKey: field, - filteredValues: select.value - } - ]); - removeFilterElement(); - }); - } - function removeFilterElement() { - filterContainer.removeChild(select); - select.removeEventListener('change', () => { - // this.successCallback(); - }); - select = null; - } - }); + select.style.top = positonRect.top + positonRect.height + 'px'; + select.style.left = positonRect.left + 'px'; + select.style.width = positonRect.width + 'px'; + select.style.height = positonRect.height + 'px'; + select.addEventListener('change', () => { + filterListSelectedValues = select.value; + tableInstance.updateFilterRules([ + { + filterKey: field, + filteredValues: select.value + } + ]); + removeFilterElement(); + }); + } + function removeFilterElement() { + filterContainer.removeChild(select); + select.removeEventListener('change', () => { + // this.successCallback(); + }); + select = null; + } + }); ``` diff --git a/docs/assets/option/en/column/base-column-type.md b/docs/assets/option/en/column/base-column-type.md index e4e22aab0..3c6dd921b 100644 --- a/docs/assets/option/en/column/base-column-type.md +++ b/docs/assets/option/en/column/base-column-type.md @@ -239,7 +239,7 @@ ${prefix} aggregation(Aggregation | CustomAggregation | Array) Not required. -Data aggregation configuration to analyze the column data. +Data aggregation summary configuration to analyze the column data. Global options can also be configured to configure aggregation rules for each column. diff --git a/docs/assets/option/en/table/listTable.md b/docs/assets/option/en/table/listTable.md index 1f2603e25..5bb5fd6dd 100644 --- a/docs/assets/option/en/table/listTable.md +++ b/docs/assets/option/en/table/listTable.md @@ -113,7 +113,7 @@ Drag the table header to move the position. Rules for frozen parts. The default ## aggregation(Aggregation|CustomAggregation|Array|Function) -Data aggregation analysis configuration, global configuration, each column will have aggregation logic, it can also be configured in the column (columns) definition, the configuration in the column has a higher priority. +Data aggregation summary analysis configuration, global configuration, each column will have aggregation logic, it can also be configured in the column (columns) definition, the configuration in the column has a higher priority. ``` aggregation?: diff --git a/docs/assets/option/zh/column/base-column-type.md b/docs/assets/option/zh/column/base-column-type.md index cf9781943..f8d9e7ead 100644 --- a/docs/assets/option/zh/column/base-column-type.md +++ b/docs/assets/option/zh/column/base-column-type.md @@ -239,7 +239,7 @@ ${prefix} aggregation(Aggregation | CustomAggregation | Array) 非必填。 -数据聚合配置,对该列数据进行分析。 +数据聚合配置,对该列数据进行汇总分析。 全局 option 也可以配置,对每一列都配置聚合规则。 diff --git a/docs/assets/option/zh/table/listTable.md b/docs/assets/option/zh/table/listTable.md index eeea66592..89ebc928e 100644 --- a/docs/assets/option/zh/table/listTable.md +++ b/docs/assets/option/zh/table/listTable.md @@ -110,7 +110,7 @@ headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI } ## aggregation(Aggregation|CustomAggregation|Array|Function) -数据聚合分析配置,全局配置每一列都将有聚合逻辑,也可以在列(columns)定义中配置,列中配置的优先级更高。 +数据聚合汇总分析配置,全局配置每一列都将有聚合逻辑,也可以在列(columns)定义中配置,列中配置的优先级更高。 ``` aggregation?: From c5d57d9b14544c796eb338e562bf5ec2c52840f6 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 20 Feb 2024 19:58:42 +0800 Subject: [PATCH 26/63] feat: chartSpec support function #1115 --- packages/vtable/examples/list/list-chart.ts | 211 ++++++++++++------ packages/vtable/examples/type/chart.ts | 134 +++++++++-- .../src/layout/chart-helper/get-chart-spec.ts | 50 ++++- .../vtable/src/layout/pivot-header-layout.ts | 17 +- .../vtable/src/layout/simple-header-layout.ts | 30 ++- .../vtable/src/scenegraph/graphic/chart.ts | 11 +- .../contributions/chart-render-helper.ts | 6 + .../graphic/contributions/chart-render.ts | 4 +- .../scenegraph/group-creater/cell-helper.ts | 9 +- .../group-creater/cell-type/chart-cell.ts | 5 +- 10 files changed, 357 insertions(+), 120 deletions(-) diff --git a/packages/vtable/examples/list/list-chart.ts b/packages/vtable/examples/list/list-chart.ts index f70740210..e68b8d8c8 100644 --- a/packages/vtable/examples/list/list-chart.ts +++ b/packages/vtable/examples/list/list-chart.ts @@ -4,7 +4,7 @@ import VChart from '@visactor/vchart'; VTable.register.chartModule('vchart', VChart); const CONTAINER_ID = 'vTable'; export function createTable() { - const columns = [ + const columns: VTable.ColumnsDefine = [ { field: 'personid', title: 'personid', @@ -22,79 +22,150 @@ export function createTable() { width: '320', cellType: 'chart', chartModule: 'vchart', - chartSpec: { - type: 'area', - data: { - id: 'data' - }, - xField: 'x', - yField: 'y', - seriesField: 'type', - point: { - style: { - fillOpacity: 1, - stroke: '#000', - strokeWidth: 4 - }, - state: { - hover: { - fillOpacity: 0.5, - stroke: 'blue', - strokeWidth: 2 + chartSpec(args) { + if (args.row % 2 == 1) + return { + type: 'area', + data: { + id: 'data' }, - selected: { - fill: 'red' - } - } - }, - area: { - style: { - fillOpacity: 0.3, - stroke: '#000', - strokeWidth: 4 - }, - state: { - hover: { - fillOpacity: 1 + xField: 'x', + yField: 'y', + seriesField: 'type', + point: { + style: { + fillOpacity: 1, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 0.5, + stroke: 'blue', + strokeWidth: 2 + }, + selected: { + fill: 'red' + } + } }, - selected: { - fill: 'red', - fillOpacity: 1 - } - } - }, - line: { - state: { - hover: { - stroke: 'red' + area: { + style: { + fillOpacity: 0.3, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 1 + }, + selected: { + fill: 'red', + fillOpacity: 1 + } + } }, - selected: { - stroke: 'yellow' - } - } - }, - - axes: [ - { - orient: 'left', - range: { - min: 0 - } - }, - { - orient: 'bottom', - label: { - visible: true + line: { + state: { + hover: { + stroke: 'red' + }, + selected: { + stroke: 'yellow' + } + } }, - type: 'band' - } - ], - legends: [ - { - visible: true, - orient: 'bottom' - } - ] + + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + }; + else + return { + type: 'common', + series: [ + { + type: 'line', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + line: { + state: { + hover: { + strokeWidth: 4 + }, + selected: { + stroke: 'red' + }, + hover_reverse: { + stroke: '#ddd' + } + } + }, + point: { + state: { + hover: { + fill: 'red' + }, + selected: { + fill: 'yellow' + }, + hover_reverse: { + fill: '#ddd' + } + } + }, + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + }; } }, { diff --git a/packages/vtable/examples/type/chart.ts b/packages/vtable/examples/type/chart.ts index 43280054b..89a5ab79d 100644 --- a/packages/vtable/examples/type/chart.ts +++ b/packages/vtable/examples/type/chart.ts @@ -439,30 +439,120 @@ export function createTable() { cellType: 'chart', chartModule: 'vchart', width: 500, - chartSpec: { - type: 'common', - series: [ - { - type: 'line', - data: { - id: 'data', - transforms: [ - { - type: 'fold', - options: { - key: 'x', // 转化后,原始数据的 key 放入这个配置对应的字段中作为值 - value: 'y', // 转化后,原始数据的 value 放入这个配置对应的字段中作为值 - fields: Object.keys(temperatureList[rowTree[0].value].day) // 需要转化的维度 + chartSpec: args => { + if (args.row % 2 === 0) { + return { + type: 'common', + series: [ + { + type: 'area', + data: { + id: 'data', + transforms: [ + { + type: 'fold', + options: { + key: 'x', // 转化后,原始数据的 key 放入这个配置对应的字段中作为值 + value: 'y', // 转化后,原始数据的 value 放入这个配置对应的字段中作为值 + fields: Object.keys(temperatureList[rowTree[0].value].month) // 需要转化的维度 + } + } + ] + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + point: { + style: { + fillOpacity: 1, + strokeWidth: 0 + }, + state: { + hover: { + fillOpacity: 0.5, + stroke: 'blue', + strokeWidth: 2 + }, + selected: { + fill: 'red' + } + } + }, + area: { + style: { + fillOpacity: 0.3, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 1 + }, + selected: { + fill: 'red', + fillOpacity: 1 + } + } + }, + line: { + state: { + hover: { + stroke: 'red' + }, + selected: { + stroke: 'yellow' + } } } - ] - }, - xField: 'x', - yField: 'y', - seriesField: 'type' - } - ], - axes: [{ orient: 'left' }, { orient: 'bottom', label: { visible: true } }] + } + ], + axes: [{ orient: 'left' }, { orient: 'bottom', label: { visible: true } }], + + markLine: [ + { + y: 0, + line: { + // 配置线样式 + style: { + lineWidth: 1, + stroke: 'black', + lineDash: [5, 5] + } + }, + endSymbol: { + style: { + visible: false + } + } + } + ] + }; + } + return { + type: 'common', + series: [ + { + type: 'line', + data: { + id: 'data', + transforms: [ + { + type: 'fold', + options: { + key: 'x', // 转化后,原始数据的 key 放入这个配置对应的字段中作为值 + value: 'y', // 转化后,原始数据的 value 放入这个配置对应的字段中作为值 + fields: Object.keys(temperatureList[rowTree[0].value].day) // 需要转化的维度 + } + } + ] + }, + xField: 'x', + yField: 'y', + seriesField: 'type' + } + ], + axes: [{ orient: 'left' }, { orient: 'bottom', label: { visible: true } }] + }; } }, { diff --git a/packages/vtable/src/layout/chart-helper/get-chart-spec.ts b/packages/vtable/src/layout/chart-helper/get-chart-spec.ts index bdfd7e117..29551db9f 100644 --- a/packages/vtable/src/layout/chart-helper/get-chart-spec.ts +++ b/packages/vtable/src/layout/chart-helper/get-chart-spec.ts @@ -20,8 +20,37 @@ export function getRawChartSpec(col: number, row: number, layout: PivotHeaderLay } const chartSpec = indicatorObj?.chartSpec; + if (typeof chartSpec === 'function') { + // 动态组织spec + const arg = { + col, + row, + dataValue: layout._table.getCellOriginValue(col, row) || '', + value: layout._table.getCellValue(col, row) || '', + rect: layout._table.getCellRangeRelativeRect(layout._table.getCellRange(col, row)), + table: layout._table + }; + return chartSpec(arg); + } return chartSpec; } +export function isShareChartSpec(col: number, row: number, layout: PivotHeaderLayoutMap): any { + const paths = layout.getCellHeaderPaths(col, row); + let indicatorObj; + if (layout.indicatorsAsCol) { + const indicatorKey = paths.colHeaderPaths.find(colPath => colPath.indicatorKey)?.indicatorKey; + indicatorObj = layout.columnObjects.find(indicator => indicator.indicatorKey === indicatorKey); + } else { + const indicatorKey = paths.rowHeaderPaths.find(rowPath => rowPath.indicatorKey)?.indicatorKey; + indicatorObj = layout.columnObjects.find(indicator => indicator.indicatorKey === indicatorKey); + } + const chartSpec = indicatorObj?.chartSpec; + + if (typeof chartSpec === 'function') { + return false; + } + return true; +} /** 检查是否有直角坐标系的图表 */ export function checkHasCartesianChart(layout: PivotHeaderLayoutMap) { let isHasCartesianChart = false; @@ -95,16 +124,19 @@ export function isHasCartesianChartInline( export function getChartSpec(col: number, row: number, layout: PivotHeaderLayoutMap): any { let chartSpec = layout.getRawChartSpec(col, row); if (chartSpec) { - chartSpec = cloneDeep(chartSpec); - chartSpec.sortDataByAxis = true; - if (isArray(chartSpec.series)) { - chartSpec.series.forEach((serie: any) => { - serie.sortDataByAxis = true; - }); + if (layout._table.isPivotChart()) { + chartSpec = cloneDeep(chartSpec); + chartSpec.sortDataByAxis = true; + if (isArray(chartSpec.series)) { + chartSpec.series.forEach((serie: any) => { + serie.sortDataByAxis = true; + }); + } + chartSpec.axes = layout.getChartAxes(col, row); + chartSpec.padding = 0; + chartSpec.dataZoom = []; // Do not support datazoom temply + return chartSpec; } - chartSpec.axes = layout.getChartAxes(col, row); - chartSpec.padding = 0; - chartSpec.dataZoom = []; // Do not support datazoom temply return chartSpec; } return null; diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index 32e2220ab..629d75f5f 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -43,7 +43,8 @@ import { getChartSpec, getRawChartSpec, isCartesianChart, - isHasCartesianChartInline + isHasCartesianChartInline, + isShareChartSpec } from './chart-helper/get-chart-spec'; import type { ITreeLayoutHeadNode, LayouTreeNode } from './tree-helper'; import { DimensionTree, countLayoutTree, dealHeader, dealHeaderForTreeMode, generateLayoutTree } from './tree-helper'; @@ -2312,6 +2313,9 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { const indicatorKey = paths.rowHeaderPaths.find(rowPath => rowPath.indicatorKey)?.indicatorKey; indicatorObj = this._indicators?.find(indicator => indicator.indicatorKey === indicatorKey); } + if (typeof indicatorObj?.chartSpec === 'function') { + return; + } indicatorObj && (indicatorObj.chartInstance = chartInstance); } @@ -2407,6 +2411,13 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { getRawChartSpec(col: number, row: number): any { return getRawChartSpec(col, row, this); } + + getChartSpec(col: number, row: number): any { + return getChartSpec(col, row, this); + } + isShareChartSpec(col: number, row: number): any { + return isShareChartSpec(col, row, this); + } getChartDataId(col: number, row: number): any { return getChartDataId(col, row, this); } @@ -2540,9 +2551,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { } return null; } - getChartSpec(col: number, row: number): any { - return getChartSpec(col, row, this); - } + /** 将_selectedDataItemsInChart保存的数据状态同步到各个图表实例中 */ _generateChartState() { const state = { diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 673051670..51a8cffc1 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -1102,6 +1102,9 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { } setChartInstance(_col: number, _row: number, chartInstance: any) { const columnObj = this.transpose ? this._columns[_row] : this._columns[_col]; + if (typeof columnObj.chartSpec === 'function') { + return; + } columnObj.chartInstance = chartInstance; } @@ -1125,9 +1128,34 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { getChartAxes(col: number, row: number): any[] { return []; } + /** 共享chartSpec 非函数 */ + isShareChartSpec(col: number, row: number): boolean { + const body = this.getBody(col, row); + const chartSpec = body?.chartSpec; + if (typeof chartSpec === 'function') { + return false; + } + return true; + } + getChartSpec(col: number, row: number) { + return this.getRawChartSpec(col, row); + } getRawChartSpec(col: number, row: number): any { const body = this.getBody(col, row); - return body?.chartSpec; + const chartSpec = body?.chartSpec; + if (typeof chartSpec === 'function') { + // 动态组织spec + const arg = { + col, + row, + dataValue: this._table.getCellOriginValue(col, row) || '', + value: this._table.getCellValue(col, row) || '', + rect: this._table.getCellRangeRelativeRect(this._table.getCellRange(col, row)), + table: this._table + }; + return chartSpec(arg); + } + return chartSpec; } getChartDataId(col: number, row: number): any { return getChartDataId(col, row, this); diff --git a/packages/vtable/src/scenegraph/graphic/chart.ts b/packages/vtable/src/scenegraph/graphic/chart.ts index e10052cf7..f13d987f2 100644 --- a/packages/vtable/src/scenegraph/graphic/chart.ts +++ b/packages/vtable/src/scenegraph/graphic/chart.ts @@ -33,14 +33,14 @@ export class Chart extends Group { activeChartInstance: any; active: boolean; cacheCanvas: HTMLCanvasElement | { x: number; y: number; width: number; height: number; canvas: HTMLCanvasElement }[]; // HTMLCanvasElement - - constructor(params: IChartGraphicAttribute) { + isShareChartSpec: boolean; //针对chartSpec用户配置成函数形式的话 就不需要存储chartInstance了 会太占内存,使用这个变量 当渲染出缓存图表会就删除chartInstance实例 + constructor(isShareChartSpec: boolean, params: IChartGraphicAttribute) { super(params); this.numberType = CHART_NUMBER_TYPE; - + this.isShareChartSpec = isShareChartSpec; // 创建chart if (!params.chartInstance) { - params.chartInstance = this.chartInstance = new params.ClassType(params.spec, { + const chartInstance = new params.ClassType(params.spec, { renderCanvas: params.canvas, mode: this.attribute.mode === 'node' ? 'node' : 'desktop-browser', modeParams: this.attribute.modeParams, @@ -59,7 +59,8 @@ export class Chart extends Group { animation: false, autoFit: false }); - this.chartInstance.renderSync(); + chartInstance.renderSync(); + params.chartInstance = this.chartInstance = chartInstance; } else { this.chartInstance = params.chartInstance; } diff --git a/packages/vtable/src/scenegraph/graphic/contributions/chart-render-helper.ts b/packages/vtable/src/scenegraph/graphic/contributions/chart-render-helper.ts index 90191830d..93fbf2dd4 100644 --- a/packages/vtable/src/scenegraph/graphic/contributions/chart-render-helper.ts +++ b/packages/vtable/src/scenegraph/graphic/contributions/chart-render-helper.ts @@ -148,6 +148,12 @@ function cacheStageCanvas(stage: IStage, chart: Chart) { const { viewWidth, viewHeight } = stage; if (viewWidth < cacheCanvasSizeLimit && viewHeight < cacheCanvasSizeLimit) { chart.cacheCanvas = stage.toCanvas(); + if (!chart.isShareChartSpec) { + // 不能整列共享chart的情况 生成完图片后即将chartInstance清除 + chart.chartInstance?.release(); + chart.chartInstance = null; + chart.setAttribute('chartInstance', null); + } return; } diff --git a/packages/vtable/src/scenegraph/graphic/contributions/chart-render.ts b/packages/vtable/src/scenegraph/graphic/contributions/chart-render.ts index 2b2a54688..fabfb1487 100644 --- a/packages/vtable/src/scenegraph/graphic/contributions/chart-render.ts +++ b/packages/vtable/src/scenegraph/graphic/contributions/chart-render.ts @@ -60,7 +60,7 @@ export class DefaultCanvasChartRender implements IGraphicRender { const viewBox = chart.getViewBox(); const { width = groupAttribute.width, height = groupAttribute.height } = chart.attribute; - const { chartInstance, active, cacheCanvas, activeChartInstance } = chart; + const { active, cacheCanvas, activeChartInstance } = chart; // console.log('render chart', chart.parent.col, chart.parent.row, viewBox, cacheCanvas); if (!active && cacheCanvas) { if (isArray(cacheCanvas)) { @@ -89,7 +89,7 @@ export class DefaultCanvasChartRender implements IGraphicRender { : data ?? [], fields: series?.data?.fields }); - if (!chartInstance.updateFullDataSync) { + if (!activeChartInstance.updateFullDataSync) { activeChartInstance.updateDataSync( dataIdStr, dataIdAndField diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index 2ff3d5208..3072fa729 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -243,13 +243,12 @@ export function createCell( padding, value, (define as ChartColumnDefine).chartModule, - table.isPivotChart() - ? (table.internalProps.layoutMap as PivotHeaderLayoutMap).getChartSpec(col, row) - : (define as ChartColumnDefine).chartSpec, + table.internalProps.layoutMap.getChartSpec(col, row), chartInstance, - (table.internalProps.layoutMap as PivotHeaderLayoutMap)?.getChartDataId(col, row) ?? 'data', + table.internalProps.layoutMap.getChartDataId(col, row) ?? 'data', table, - cellTheme + cellTheme, + table.internalProps.layoutMap.isShareChartSpec(col, row) ); } else if (type === 'progressbar') { const style = table._getCellStyle(col, row) as ProgressBarStyle; 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 5c141bbe2..06a871aca 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 @@ -21,7 +21,8 @@ export function createChartCellGroup( chartInstance: any, dataId: string | Record, table: BaseTableAPI, - cellTheme: IThemeSpec + cellTheme: IThemeSpec, + isShareChartSpec: true ) { // 获取注册的chart图表类型 const registerCharts = registerChartTypes.get(); @@ -62,7 +63,7 @@ export function createChartCellGroup( } cellGroup.AABBBounds.width(); // TODO 需要底层VRender修改 // chart - const chartGroup = new Chart({ + const chartGroup = new Chart(isShareChartSpec, { stroke: false, x: padding[3], y: padding[0], From aaa8068e1ad4ef8f02b6a64399310d023066c13c Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 20 Feb 2024 19:59:02 +0800 Subject: [PATCH 27/63] docs: update changlog of rush --- ...e-chartspec-support-function_2024-02-20-11-59.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1115-feature-chartspec-support-function_2024-02-20-11-59.json diff --git a/common/changes/@visactor/vtable/1115-feature-chartspec-support-function_2024-02-20-11-59.json b/common/changes/@visactor/vtable/1115-feature-chartspec-support-function_2024-02-20-11-59.json new file mode 100644 index 000000000..dc0457eaf --- /dev/null +++ b/common/changes/@visactor/vtable/1115-feature-chartspec-support-function_2024-02-20-11-59.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: chartSpec support function #1115\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From fae324d30bdf6d27ab65927dc290602285a92803 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 21 Feb 2024 15:19:27 +0800 Subject: [PATCH 28/63] docs: add demo for chart type in list table --- docs/assets/demo/en/cell-type/list-chart.md | 543 ++++++++++++++++++ .../demo/en/cell-type/list-table-chart.md | 2 +- docs/assets/demo/menu.json | 17 +- docs/assets/demo/zh/cell-type/list-chart.md | 542 +++++++++++++++++ docs/assets/guide/en/cell_type/chart.md | 2 +- docs/assets/guide/zh/cell_type/chart.md | 2 +- .../option/en/column/chart-column-type.md | 2 +- .../en/indicator/chart-indicator-type.md | 2 +- .../option/zh/column/chart-column-type.md | 2 +- .../zh/indicator/chart-indicator-type.md | 2 +- packages/vtable/examples/list/list-chart.ts | 183 +----- 11 files changed, 1119 insertions(+), 180 deletions(-) create mode 100644 docs/assets/demo/en/cell-type/list-chart.md create mode 100644 docs/assets/demo/zh/cell-type/list-chart.md diff --git a/docs/assets/demo/en/cell-type/list-chart.md b/docs/assets/demo/en/cell-type/list-chart.md new file mode 100644 index 000000000..b719c52eb --- /dev/null +++ b/docs/assets/demo/en/cell-type/list-chart.md @@ -0,0 +1,543 @@ +--- +category: examples +group: Cell Type +title: List table integrated chart +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-chart-multiple.png +link: '../guide/cell_type/chart' +option: ListTable-columns-chart#cellType +--- + +# Basic table integrated chart + +Combine vchart chart library and render it into tables to enrich visual display forms and improve multi-chart rendering performance. This example refers to vchart’s bar line pie chart. For details, please refer to: https://visactor.io/vchart/demo/progress/linear-progress-with-target-value + +## Key Configurations + +- `VTable.register.chartModule('vchart', VChart)` registers the chart library for drawing charts. Currently supports VChart +- `cellType: 'chart'` specifies the type chart +- `chartModule: 'vchart'` specifies the registered chart library name +- `chartSpec: {}` chart spec + +## Code Demo + +```javascript livedemo template=vtable + VTable.register.chartModule('vchart', VChart); + const columns = [ + { + field: 'id', + title: 'id', + sort: true, + width: 80, + style: { + textAlign: 'left', + bgColor: '#ea9999' + } + }, + { + field: 'areaChart', + title: 'multiple vchart type', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec(args) { + if (args.row % 3 == 2) + return { + type: 'area', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + point: { + style: { + fillOpacity: 1, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 0.5, + stroke: 'blue', + strokeWidth: 2 + }, + selected: { + fill: 'red' + } + } + }, + area: { + style: { + fillOpacity: 0.3, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 1 + }, + selected: { + fill: 'red', + fillOpacity: 1 + } + } + }, + line: { + state: { + hover: { + stroke: 'red' + }, + selected: { + stroke: 'yellow' + } + } + }, + + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + }; + else if (args.row % 3 == 1) + return { + type: 'common', + series: [ + { + type: 'line', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + line: { + state: { + hover: { + strokeWidth: 4 + }, + selected: { + stroke: 'red' + }, + hover_reverse: { + stroke: '#ddd' + } + } + }, + point: { + state: { + hover: { + fill: 'red' + }, + selected: { + fill: 'yellow' + }, + hover_reverse: { + fill: '#ddd' + } + } + }, + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + }; + return { + type: 'pie', + data: { id: 'data1' }, + categoryField: 'y', + valueField: 'x' + } + } + }, + { + field: 'lineChart', + title: 'vchart line', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec: { + type: 'common', + series: [ + { + type: 'line', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + line: { + state: { + hover: { + strokeWidth: 4 + }, + selected: { + stroke: 'red' + }, + hover_reverse: { + stroke: '#ddd' + } + } + }, + point: { + state: { + hover: { + fill: 'red' + }, + selected: { + fill: 'yellow' + }, + hover_reverse: { + fill: '#ddd' + } + } + }, + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + } + }, + { + field: 'barChart', + title: 'vchart bar', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec: { + type: 'common', + series: [ + { + type: 'bar', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + bar: { + state: { + hover: { + fill: 'green' + }, + selected: { + fill: 'orange' + }, + hover_reverse: { + fill: '#ccc' + } + } + } + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ] + } + }, + { + field: 'scatterChart', + title: 'vchart scatter', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec: { + type: 'common', + series: [ + { + type: 'scatter', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type' + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ] + } + }, + { + field: 'areaChart', + title: 'vchart area', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec: { + type: 'common', + series: [ + { + type: 'area', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + point: { + style: { + fillOpacity: 1, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 0.5, + stroke: 'blue', + strokeWidth: 2 + }, + selected: { + fill: 'red' + } + } + }, + area: { + style: { + fillOpacity: 0.3, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 1 + }, + selected: { + fill: 'red', + fillOpacity: 1 + } + } + }, + line: { + state: { + hover: { + stroke: 'red' + }, + selected: { + stroke: 'yellow' + } + } + } + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + } + }, + ]; + const records = []; + for (let i = 1; i <= 10; i++) + records.push({ + id: i, + areaChart: [ + { x: '0', type: 'A', y: 100 * i }, + { x: '1', type: 'A', y: '707' }, + { x: '2', type: 'A', y: '832' }, + { x: '3', type: 'A', y: '726' }, + { x: '4', type: 'A', y: '756' }, + { x: '5', type: 'A', y: '777' }, + { x: '6', type: 'A', y: '689' }, + { x: '7', type: 'A', y: '795' }, + { x: '8', type: 'A', y: '889' }, + { x: '9', type: 'A', y: '757' }, + { x: '0', type: 'B', y: '773' }, + { x: '1', type: 'B', y: '785' }, + { x: '2', type: 'B', y: '635' }, + { x: '3', type: 'B', y: '813' }, + { x: '4', type: 'B', y: '678' }, + { x: '5', type: 'B', y: 796 + 100 * i }, + { x: '6', type: 'B', y: '652' }, + { x: '7', type: 'B', y: '623' }, + { x: '8', type: 'B', y: '649' }, + { x: '9', type: 'B', y: '630' } + ], + lineChart: [ + { x: '0', type: 'A', y: 100 * i }, + { x: '1', type: 'A', y: '707' }, + { x: '2', type: 'A', y: '832' }, + { x: '3', type: 'A', y: '726' }, + { x: '4', type: 'A', y: '756' }, + { x: '5', type: 'A', y: '777' }, + { x: '6', type: 'A', y: '689' }, + { x: '7', type: 'A', y: '795' }, + { x: '8', type: 'A', y: '889' }, + { x: '9', type: 'A', y: '757' }, + { x: '0', type: 'B', y: 500 }, + { x: '1', type: 'B', y: '785' }, + { x: '2', type: 'B', y: '635' }, + { x: '3', type: 'B', y: '813' }, + { x: '4', type: 'B', y: '678' }, + { x: '5', type: 'B', y: '796' }, + { x: '6', type: 'B', y: '652' }, + { x: '7', type: 'B', y: '623' }, + { x: '8', type: 'B', y: '649' }, + { x: '9', type: 'B', y: '630' } + ], + barChart: [ + { x: '0', type: 'A', y: 100 * i }, + { x: '1', type: 'A', y: '707' }, + { x: '2', type: 'A', y: '832' }, + { x: '3', type: 'A', y: '726' }, + { x: '4', type: 'A', y: '756' }, + { x: '5', type: 'A', y: '777' }, + { x: '6', type: 'A', y: '689' }, + { x: '7', type: 'A', y: '795' }, + { x: '8', type: 'A', y: '889' }, + { x: '9', type: 'A', y: '757' }, + { x: '0', type: 'B', y: 500 }, + { x: '1', type: 'B', y: '785' }, + { x: '2', type: 'B', y: '635' }, + { x: '3', type: 'B', y: '813' }, + { x: '4', type: 'B', y: '678' }, + { x: '5', type: 'B', y: '796' }, + { x: '6', type: 'B', y: '652' }, + { x: '7', type: 'B', y: '623' }, + { x: '8', type: 'B', y: '649' }, + { x: '9', type: 'B', y: '630' } + ], + scatterChart: [ + { x: '0', type: 'A', y: 100 * i }, + { x: '1', type: 'A', y: '707' }, + { x: '2', type: 'A', y: '832' }, + { x: '3', type: 'A', y: '726' }, + { x: '4', type: 'A', y: '756' }, + { x: '5', type: 'A', y: '777' }, + { x: '6', type: 'A', y: '689' }, + { x: '7', type: 'A', y: '795' }, + { x: '8', type: 'A', y: '889' }, + { x: '9', type: 'A', y: '757' }, + { x: '0', type: 'B', y: 500 }, + { x: '1', type: 'B', y: '785' }, + { x: '2', type: 'B', y: '635' }, + { x: '3', type: 'B', y: '813' }, + { x: '4', type: 'B', y: '678' }, + { x: '5', type: 'B', y: '796' }, + { x: '6', type: 'B', y: '652' }, + { x: '7', type: 'B', y: '623' }, + { x: '8', type: 'B', y: '649' }, + { x: '9', type: 'B', y: '630' } + ] + }); + const option = { + records, + columns, + transpose: false, + defaultColWidth: 200, + defaultRowHeight: 200, + defaultHeaderRowHeight: 50 + }; + +const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID),option); +window['tableInstance'] = tableInstance; +``` diff --git a/docs/assets/demo/en/cell-type/list-table-chart.md b/docs/assets/demo/en/cell-type/list-table-chart.md index 15d8a5b3b..15c514a11 100644 --- a/docs/assets/demo/en/cell-type/list-table-chart.md +++ b/docs/assets/demo/en/cell-type/list-table-chart.md @@ -7,7 +7,7 @@ link: '../guide/cell_type/chart' option: ListTable-columns-chart#cellType --- -# Basic table integrated chart +# List table integrated chart Combine vchart chart library and render it into tables to enrich visual display forms and improve multi-chart rendering performance. This example refers to vchart’s bar progress bar. For details, please refer to: https://visactor.io/vchart/demo/progress/linear-progress-with-target-value diff --git a/docs/assets/demo/menu.json b/docs/assets/demo/menu.json index 9881b2b98..cc7b0673d 100644 --- a/docs/assets/demo/menu.json +++ b/docs/assets/demo/menu.json @@ -247,17 +247,24 @@ } }, { - "path": "chart", + "path": "list-chart", "title": { - "zh": "透视表图表展示", - "en": "Chart Type In PivotTable" + "zh": "基本表格集成图表", + "en": "Chart Type In ListTable" } }, { "path": "list-table-chart", "title": { - "zh": "基本表格图表展示", - "en": "Chart Type In ListTable" + "zh": "基本表格集成图表 2", + "en": "Chart Type In ListTable 2" + } + }, + { + "path": "chart", + "title": { + "zh": "透视表图表展示", + "en": "Chart Type In PivotTable" } }, { diff --git a/docs/assets/demo/zh/cell-type/list-chart.md b/docs/assets/demo/zh/cell-type/list-chart.md new file mode 100644 index 000000000..d88eeaef2 --- /dev/null +++ b/docs/assets/demo/zh/cell-type/list-chart.md @@ -0,0 +1,542 @@ +--- +category: examples +group: Cell Type +title: 基本表格集成图表 +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-chart-multiple.png +link: '../guide/cell_type/chart' +option: ListTable-columns-chart#cellType +--- + +# 图表类型集成到透视表 + +将vchart图表库结合渲染到表格中,丰富可视化展示形式,提升多图表渲染性能。该示例引用了vchart的线柱饼面图,具体可参考:https://visactor.io/vchart/demo/progress/linear-progress-with-target-value + +## 关键配置 + +- `VTable.register.chartModule('vchart', VChart)` 注册绘制图表的图表库 目前支持VChart +- `cellType: 'chart'` 指定类型chart +- `chartModule: 'vchart'` 指定注册的图表库名称 +- `chartSpec: {}|Function` 图表spec +## 代码演示 + +```javascript livedemo template=vtable + VTable.register.chartModule('vchart', VChart); + const columns = [ + { + field: 'id', + title: 'id', + sort: true, + width: 80, + style: { + textAlign: 'left', + bgColor: '#ea9999' + } + }, + { + field: 'areaChart', + title: 'multiple vchart type', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec(args) { + if (args.row % 3 == 2) + return { + type: 'area', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + point: { + style: { + fillOpacity: 1, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 0.5, + stroke: 'blue', + strokeWidth: 2 + }, + selected: { + fill: 'red' + } + } + }, + area: { + style: { + fillOpacity: 0.3, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 1 + }, + selected: { + fill: 'red', + fillOpacity: 1 + } + } + }, + line: { + state: { + hover: { + stroke: 'red' + }, + selected: { + stroke: 'yellow' + } + } + }, + + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + }; + else if (args.row % 3 == 1) + return { + type: 'common', + series: [ + { + type: 'line', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + line: { + state: { + hover: { + strokeWidth: 4 + }, + selected: { + stroke: 'red' + }, + hover_reverse: { + stroke: '#ddd' + } + } + }, + point: { + state: { + hover: { + fill: 'red' + }, + selected: { + fill: 'yellow' + }, + hover_reverse: { + fill: '#ddd' + } + } + }, + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + }; + return { + type: 'pie', + data: { id: 'data1' }, + categoryField: 'y', + valueField: 'x' + } + } + }, + { + field: 'lineChart', + title: 'vchart line', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec: { + type: 'common', + series: [ + { + type: 'line', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + line: { + state: { + hover: { + strokeWidth: 4 + }, + selected: { + stroke: 'red' + }, + hover_reverse: { + stroke: '#ddd' + } + } + }, + point: { + state: { + hover: { + fill: 'red' + }, + selected: { + fill: 'yellow' + }, + hover_reverse: { + fill: '#ddd' + } + } + }, + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + } + }, + { + field: 'barChart', + title: 'vchart bar', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec: { + type: 'common', + series: [ + { + type: 'bar', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + bar: { + state: { + hover: { + fill: 'green' + }, + selected: { + fill: 'orange' + }, + hover_reverse: { + fill: '#ccc' + } + } + } + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ] + } + }, + { + field: 'scatterChart', + title: 'vchart scatter', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec: { + type: 'common', + series: [ + { + type: 'scatter', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type' + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ] + } + }, + { + field: 'areaChart', + title: 'vchart area', + width: '320', + cellType: 'chart', + chartModule: 'vchart', + chartSpec: { + type: 'common', + series: [ + { + type: 'area', + data: { + id: 'data' + }, + xField: 'x', + yField: 'y', + seriesField: 'type', + point: { + style: { + fillOpacity: 1, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 0.5, + stroke: 'blue', + strokeWidth: 2 + }, + selected: { + fill: 'red' + } + } + }, + area: { + style: { + fillOpacity: 0.3, + stroke: '#000', + strokeWidth: 4 + }, + state: { + hover: { + fillOpacity: 1 + }, + selected: { + fill: 'red', + fillOpacity: 1 + } + } + }, + line: { + state: { + hover: { + stroke: 'red' + }, + selected: { + stroke: 'yellow' + } + } + } + } + ], + axes: [ + { + orient: 'left', + range: { + min: 0 + } + }, + { + orient: 'bottom', + label: { + visible: true + }, + type: 'band' + } + ], + legends: [ + { + visible: true, + orient: 'bottom' + } + ] + } + }, + ]; + const records = []; + for (let i = 1; i <= 10; i++) + records.push({ + id: i, + areaChart: [ + { x: '0', type: 'A', y: 100 * i }, + { x: '1', type: 'A', y: '707' }, + { x: '2', type: 'A', y: '832' }, + { x: '3', type: 'A', y: '726' }, + { x: '4', type: 'A', y: '756' }, + { x: '5', type: 'A', y: '777' }, + { x: '6', type: 'A', y: '689' }, + { x: '7', type: 'A', y: '795' }, + { x: '8', type: 'A', y: '889' }, + { x: '9', type: 'A', y: '757' }, + { x: '0', type: 'B', y: '773' }, + { x: '1', type: 'B', y: '785' }, + { x: '2', type: 'B', y: '635' }, + { x: '3', type: 'B', y: '813' }, + { x: '4', type: 'B', y: '678' }, + { x: '5', type: 'B', y: 796 + 100 * i }, + { x: '6', type: 'B', y: '652' }, + { x: '7', type: 'B', y: '623' }, + { x: '8', type: 'B', y: '649' }, + { x: '9', type: 'B', y: '630' } + ], + lineChart: [ + { x: '0', type: 'A', y: 100 * i }, + { x: '1', type: 'A', y: '707' }, + { x: '2', type: 'A', y: '832' }, + { x: '3', type: 'A', y: '726' }, + { x: '4', type: 'A', y: '756' }, + { x: '5', type: 'A', y: '777' }, + { x: '6', type: 'A', y: '689' }, + { x: '7', type: 'A', y: '795' }, + { x: '8', type: 'A', y: '889' }, + { x: '9', type: 'A', y: '757' }, + { x: '0', type: 'B', y: 500 }, + { x: '1', type: 'B', y: '785' }, + { x: '2', type: 'B', y: '635' }, + { x: '3', type: 'B', y: '813' }, + { x: '4', type: 'B', y: '678' }, + { x: '5', type: 'B', y: '796' }, + { x: '6', type: 'B', y: '652' }, + { x: '7', type: 'B', y: '623' }, + { x: '8', type: 'B', y: '649' }, + { x: '9', type: 'B', y: '630' } + ], + barChart: [ + { x: '0', type: 'A', y: 100 * i }, + { x: '1', type: 'A', y: '707' }, + { x: '2', type: 'A', y: '832' }, + { x: '3', type: 'A', y: '726' }, + { x: '4', type: 'A', y: '756' }, + { x: '5', type: 'A', y: '777' }, + { x: '6', type: 'A', y: '689' }, + { x: '7', type: 'A', y: '795' }, + { x: '8', type: 'A', y: '889' }, + { x: '9', type: 'A', y: '757' }, + { x: '0', type: 'B', y: 500 }, + { x: '1', type: 'B', y: '785' }, + { x: '2', type: 'B', y: '635' }, + { x: '3', type: 'B', y: '813' }, + { x: '4', type: 'B', y: '678' }, + { x: '5', type: 'B', y: '796' }, + { x: '6', type: 'B', y: '652' }, + { x: '7', type: 'B', y: '623' }, + { x: '8', type: 'B', y: '649' }, + { x: '9', type: 'B', y: '630' } + ], + scatterChart: [ + { x: '0', type: 'A', y: 100 * i }, + { x: '1', type: 'A', y: '707' }, + { x: '2', type: 'A', y: '832' }, + { x: '3', type: 'A', y: '726' }, + { x: '4', type: 'A', y: '756' }, + { x: '5', type: 'A', y: '777' }, + { x: '6', type: 'A', y: '689' }, + { x: '7', type: 'A', y: '795' }, + { x: '8', type: 'A', y: '889' }, + { x: '9', type: 'A', y: '757' }, + { x: '0', type: 'B', y: 500 }, + { x: '1', type: 'B', y: '785' }, + { x: '2', type: 'B', y: '635' }, + { x: '3', type: 'B', y: '813' }, + { x: '4', type: 'B', y: '678' }, + { x: '5', type: 'B', y: '796' }, + { x: '6', type: 'B', y: '652' }, + { x: '7', type: 'B', y: '623' }, + { x: '8', type: 'B', y: '649' }, + { x: '9', type: 'B', y: '630' } + ] + }); + const option = { + records, + columns, + transpose: false, + defaultColWidth: 200, + defaultRowHeight: 200, + defaultHeaderRowHeight: 50 + }; + +const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID),option); +window['tableInstance'] = tableInstance; +``` diff --git a/docs/assets/guide/en/cell_type/chart.md b/docs/assets/guide/en/cell_type/chart.md index 578c199a6..f2536ee46 100644 --- a/docs/assets/guide/en/cell_type/chart.md +++ b/docs/assets/guide/en/cell_type/chart.md @@ -17,7 +17,7 @@ Table display type`cellType`Set to`chart`Used to generate charts. * cellType: 'chart'//chart chart type * chartModule: 'vchart'//vchart is the name configured during registration -* Chart Spec :{ } // chart configuration item +* Chart Spec :{ } // chart configuration item, support funciton define Where the chartSpec configuration item corresponds[VChart configuration](https://visactor.io/vchart/option) diff --git a/docs/assets/guide/zh/cell_type/chart.md b/docs/assets/guide/zh/cell_type/chart.md index cb5bb4284..e4334dc48 100644 --- a/docs/assets/guide/zh/cell_type/chart.md +++ b/docs/assets/guide/zh/cell_type/chart.md @@ -16,7 +16,7 @@ VTable.register.chartModule('vchart', VChart); 表格展示类型`cellType`设置成`chart`用于生成图表。 - cellType: 'chart' //chart图表类型 - chartModule: 'vchart' // vchart是注册时配置的名称 -- chartSpec:{ } //chart配置项 +- chartSpec:{ } //chart配置项 支持函数形式返回spec 其中chartSpec配置项对应[VChart配置](https://visactor.io/vchart/option) diff --git a/docs/assets/option/en/column/chart-column-type.md b/docs/assets/option/en/column/chart-column-type.md index 5a440eabc..669d31391 100644 --- a/docs/assets/option/en/column/chart-column-type.md +++ b/docs/assets/option/en/column/chart-column-type.md @@ -22,4 +22,4 @@ Corresponding to the injected chart library component name **Chart type exclusive configuration options** -Corresponding to the chart library's spec, the value is provided in the records +Set the spec of the chart, or set it to a function that returns a different spec. The data displayed in the chart is provided by records. diff --git a/docs/assets/option/en/indicator/chart-indicator-type.md b/docs/assets/option/en/indicator/chart-indicator-type.md index b235567d3..ff81adccf 100644 --- a/docs/assets/option/en/indicator/chart-indicator-type.md +++ b/docs/assets/option/en/indicator/chart-indicator-type.md @@ -18,7 +18,7 @@ Corresponding to the name of the injected chart library component, the injection **Exclusive configuration options for chart type** -Corresponds to the chart library's spec, where value corresponds to the provided records +Set the spec of the chart, or set it to a function that returns a different spec. The data displayed in the chart is provided by records. {{ use: base-indicator-type( prefix = '##'+${prefix} diff --git a/docs/assets/option/zh/column/chart-column-type.md b/docs/assets/option/zh/column/chart-column-type.md index 509c032c6..376e3ba30 100644 --- a/docs/assets/option/zh/column/chart-column-type.md +++ b/docs/assets/option/zh/column/chart-column-type.md @@ -22,4 +22,4 @@ **chart 类型专属配置项** -对应图表库的 spec 其中 value 对应在 records 中提供 +设置图表的 spec,或者设置成函数返回不同的spec。其中显示在图表的数据是对应在 records 中提供。 diff --git a/docs/assets/option/zh/indicator/chart-indicator-type.md b/docs/assets/option/zh/indicator/chart-indicator-type.md index 74e1159b8..3501d0fc7 100644 --- a/docs/assets/option/zh/indicator/chart-indicator-type.md +++ b/docs/assets/option/zh/indicator/chart-indicator-type.md @@ -18,7 +18,7 @@ **chart 类型专属配置项** -对应图表库的 spec,其中图表所需数据会对应在 records 中提供。 +设置图表的 spec,或者设置成函数返回不同的spec。其中显示在图表的数据由 records 提供。 {{ use: base-indicator-type( prefix = '##'+${prefix} diff --git a/packages/vtable/examples/list/list-chart.ts b/packages/vtable/examples/list/list-chart.ts index e68b8d8c8..4e28ef3a7 100644 --- a/packages/vtable/examples/list/list-chart.ts +++ b/packages/vtable/examples/list/list-chart.ts @@ -6,8 +6,8 @@ const CONTAINER_ID = 'vTable'; export function createTable() { const columns: VTable.ColumnsDefine = [ { - field: 'personid', - title: 'personid', + field: 'id', + title: 'id', description: '这是一个标题的详细描述', sort: true, width: 80, @@ -18,12 +18,12 @@ export function createTable() { }, { field: 'areaChart', - title: 'vchart area', + title: 'multiple vchart type', width: '320', cellType: 'chart', chartModule: 'vchart', chartSpec(args) { - if (args.row % 2 == 1) + if (args.row % 3 == 2) return { type: 'area', data: { @@ -98,7 +98,7 @@ export function createTable() { } ] }; - else + else if (args.row % 3 == 1) return { type: 'common', series: [ @@ -166,6 +166,12 @@ export function createTable() { } ] }; + return { + type: 'pie', + data: { id: 'data1' }, + categoryField: 'y', + valueField: 'x' + }; } }, { @@ -244,7 +250,7 @@ export function createTable() { }, { field: 'barChart', - title: 'vchart line', + title: 'vchart bar', width: '320', cellType: 'chart', chartModule: 'vchart', @@ -293,7 +299,7 @@ export function createTable() { }, { field: 'scatterChart', - title: 'vchart line', + title: 'vchart scatter', width: '320', cellType: 'chart', chartModule: 'vchart', @@ -411,171 +417,12 @@ export function createTable() { } ] } - }, - { - field: 'lineChart', - title: 'vchart line', - width: '320', - cellType: 'chart', - chartModule: 'vchart', - chartSpec: { - type: 'common', - series: [ - { - type: 'line', - data: { - id: 'data' - }, - xField: 'x', - yField: 'y', - seriesField: 'type', - line: { - state: { - hover: { - strokeWidth: 4 - }, - selected: { - stroke: 'red' - }, - hover_reverse: { - stroke: '#ddd' - } - } - }, - point: { - state: { - hover: { - fill: 'red' - }, - selected: { - fill: 'yellow' - }, - hover_reverse: { - fill: '#ddd' - } - } - }, - legends: [ - { - visible: true, - orient: 'bottom' - } - ] - } - ], - axes: [ - { - orient: 'left', - range: { - min: 0 - } - }, - { - orient: 'bottom', - label: { - visible: true - }, - type: 'band' - } - ], - legends: [ - { - visible: true, - orient: 'bottom' - } - ] - } - }, - { - field: 'barChart', - title: 'vchart line', - width: '320', - cellType: 'chart', - chartModule: 'vchart', - chartSpec: { - type: 'common', - series: [ - { - type: 'bar', - data: { - id: 'data' - }, - xField: 'x', - yField: 'y', - seriesField: 'type', - bar: { - state: { - hover: { - fill: 'green' - }, - selected: { - fill: 'orange' - }, - hover_reverse: { - fill: '#ccc' - } - } - } - } - ], - axes: [ - { - orient: 'left', - range: { - min: 0 - } - }, - { - orient: 'bottom', - label: { - visible: true - }, - type: 'band' - } - ] - } - }, - { - field: 'scatterChart', - title: 'vchart line', - width: '320', - cellType: 'chart', - chartModule: 'vchart', - chartSpec: { - type: 'common', - series: [ - { - type: 'scatter', - data: { - id: 'data' - }, - xField: 'x', - yField: 'y', - seriesField: 'type' - } - ], - axes: [ - { - orient: 'left', - range: { - min: 0 - } - }, - { - orient: 'bottom', - label: { - visible: true - }, - type: 'band' - } - ] - } } ]; const records: any[] = []; // = generatePersonsDataSource(10); - for (let i = 1; i <= 40; i++) + for (let i = 1; i <= 10; i++) records.push({ - personid: i, + id: i, areaChart: [ { x: '0', type: 'A', y: 100 * i }, { x: '1', type: 'A', y: '707' }, From 5126a891420e8459146870d5b17d60e54aff4a48 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 21 Feb 2024 20:22:29 +0800 Subject: [PATCH 29/63] feat: add api getRecordIndexByCell #1121 --- docs/assets/api/en/methods.md | 16 ++++++++++ docs/assets/api/zh/methods.md | 16 ++++++++++ packages/vtable/examples/list/list-tree.ts | 2 +- packages/vtable/src/ListTable.ts | 13 ++++++-- packages/vtable/src/data/DataSource.ts | 31 ++++++++++++------- .../vtable/src/layout/pivot-header-layout.ts | 2 +- .../vtable/src/layout/simple-header-layout.ts | 2 +- .../src/ts-types/list-table/layout-map/api.ts | 2 +- 8 files changed, 66 insertions(+), 18 deletions(-) diff --git a/docs/assets/api/en/methods.md b/docs/assets/api/en/methods.md index 692db9a51..f4235a7f5 100644 --- a/docs/assets/api/en/methods.md +++ b/docs/assets/api/en/methods.md @@ -303,6 +303,22 @@ Note: ListTable specific interface getTableIndexByRecordIndex: (recordIndex: number) => number; ``` +## getRecordIndexByCell(Function) + +Get the number of data in the current cell in the data source. + +If it is a table in tree mode, an array will be returned, such as [1,2], the 3rd item in the children of the 2nd item in the data source. + +** ListTable proprietary ** + +``` + /** Get the number of the data in the current cell in the data source. + * If it is a table in tree mode, an array will be returned, such as [1,2], the 3rd item in the children of the 2nd item in the data source + * Note: ListTable specific interface */ + getRecordIndexByCell(col: number, row: number): number | number[] +** ListTable proprietary ** +``` + ## getTableIndexByField(Function) Get the index row number or column number displayed in the table according to the field of the data source (Related to transposition, the non-transposition obtains the row number, and the transposed table obtains the column number). diff --git a/docs/assets/api/zh/methods.md b/docs/assets/api/zh/methods.md index 2bbf791be..cacc07a8c 100644 --- a/docs/assets/api/zh/methods.md +++ b/docs/assets/api/zh/methods.md @@ -301,6 +301,22 @@ setRecords(records: Array, sort?: SortState | SortState[]) //** 基本表 getTableIndexByRecordIndex: (recordIndex: number) => number; ``` +## getRecordIndexByCell(Function) + +获取当前单元格的数据是数据源中的第几条。 + +如果是树形模式的表格,将返回数组,如[1,2] 数据源中第2条数据中children中的第3条。 + +** ListTable 专有 ** + +``` + /** 获取当前单元格的数据是数据源中的第几条。 + * 如果是树形模式的表格,将返回数组,如[1,2] 数据源中第2条数据中children中的第3条 + * 注:ListTable特有接口 */ + getRecordIndexByCell(col: number, row: number): number | number[] +** ListTable 专有 ** +``` + ## getTableIndexByField(Function) 根据数据源的 field 获取显示到表格中的 index 行号或者列号(与转置相关,非转置获取的是行号,转置表获取的是列号)。 diff --git a/packages/vtable/examples/list/list-tree.ts b/packages/vtable/examples/list/list-tree.ts index 019880993..2cd4a05e3 100644 --- a/packages/vtable/examples/list/list-tree.ts +++ b/packages/vtable/examples/list/list-tree.ts @@ -165,7 +165,7 @@ export function createTable() { sort: true } ], - showPin: true, //显示VTable内置冻结列图标 + showFrozenIcon: true, //显示VTable内置冻结列图标 widthMode: 'standard', autoFillHeight: true, // heightMode: 'adaptive', diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 32337ab3a..d0dfe5770 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -267,10 +267,19 @@ export class ListTable extends BaseTable implements ListTableAPI { /** 获取当前单元格在body部分的展示索引 即(row / col)-headerLevelCount。注:ListTable特有接口 */ getRecordShowIndexByCell(col: number, row: number): number { const { layoutMap } = this.internalProps; - return layoutMap.getRecordIndexByCell(col, row); + return layoutMap.getRecordShowIndexByCell(col, row); } - getTableIndexByRecordIndex(recordIndex: number) { + /** 获取当前单元格的数据是数据源中的第几条。 + * 如果是树形模式的表格,将返回数组,如[1,2] 数据源中第2条数据中children中的第3条 + * 注:ListTable特有接口 */ + getRecordIndexByCell(col: number, row: number): number | number[] { + const { layoutMap } = this.internalProps; + const recordShowIndex = layoutMap.getRecordShowIndexByCell(col, row); + return this.dataSource.currentPagerIndexedData[recordShowIndex]; + } + + getTableIndexByRecordIndex(recordIndex: number | number[]) { if (this.transpose) { return this.dataSource.getTableIndex(recordIndex) + this.rowHeaderLevelCount; } diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index e6cd02202..d14a24592 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -18,7 +18,7 @@ import { applyChainSafe, getOrApply, obj, isPromise, emptyFn } from '../tools/he import { EventTarget } from '../event/EventTarget'; import { getValueByPath, isAllDigits } from '../tools/util'; import { calculateArrayDiff } from '../tools/diff-cell'; -import { cloneDeep, isValid } from '@visactor/vutils'; +import { arrayEqual, cloneDeep, isValid } from '@visactor/vutils'; import type { BaseTableAPI } from '../ts-types/base-table'; import { RecordAggregator, @@ -164,7 +164,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { /** 当前页每一行对应源数据的索引 */ protected _currentPagerIndexedData: (number | number[])[]; // 当前是否为层级的树形结构 排序时判断该值确实是否继续进行子节点排序 - enableHierarchyState = false; + hierarchyExpandLevel: number = 0; static get EVENT_TYPE(): typeof EVENT_TYPE { return EVENT_TYPE; } @@ -205,18 +205,23 @@ export class DataSource extends EventTarget implements DataSourceAPI { currentPage: 0 }; if (hierarchyExpandLevel >= 1) { - this.enableHierarchyState = true; + this.hierarchyExpandLevel = hierarchyExpandLevel; } - // 初始化currentIndexedData 正常未排序。设置其状态 this.currentIndexedData = Array.from({ length: this._sourceLength }, (_, i) => i); - if (this.enableHierarchyState) { + // 初始化currentIndexedData 正常未排序。设置其状态 + this.initTreeHierarchyState(); + this.updatePagerData(); + } + initTreeHierarchyState() { + if (this.hierarchyExpandLevel) { + this.treeDataHierarchyState = new Map(); for (let i = 0; i < this._sourceLength; i++) { //expandLevel为有效值即需要按tree分析展示数据 const nodeData = this.getOriginalRecord(i); (nodeData as any).children && this.treeDataHierarchyState.set(i, HierarchyState.collapse); } - } - if (hierarchyExpandLevel > 1) { + + this.currentIndexedData = Array.from({ length: this._sourceLength }, (_, i) => i); let nodeLength = this._sourceLength; for (let i = 0; i < nodeLength; i++) { const indexKey = this.currentIndexedData[i]; @@ -226,13 +231,12 @@ export class DataSource extends EventTarget implements DataSourceAPI { Array.isArray(indexKey) ? indexKey.join(',') : indexKey, HierarchyState.expand ); - const childrenLength = this.initChildrenNodeHierarchy(indexKey, hierarchyExpandLevel, 2, nodeData); + const childrenLength = this.initChildrenNodeHierarchy(indexKey, this.hierarchyExpandLevel, 2, nodeData); i += childrenLength; nodeLength += childrenLength; } } } - this.updatePagerData(); } //将聚合类型注册 收集到aggregators registerAggregator(type: string, aggregator: any) { @@ -416,7 +420,10 @@ export class DataSource extends EventTarget implements DataSourceAPI { getIndexKey(index: number): number | number[] { return _getIndex(this.currentPagerIndexedData, index); } - getTableIndex(colOrRow: number): number { + getTableIndex(colOrRow: number | number[]): number { + if (Array.isArray(colOrRow)) { + return this.currentPagerIndexedData.findIndex(value => arrayEqual(value, colOrRow)); + } return this.currentPagerIndexedData.findIndex(value => value === colOrRow); } /** 获取数据源中第index位置的field字段数据。传入col row是因为后面的format函数参数使用*/ @@ -628,7 +635,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { this.source.splice(index, 0, record); this.currentIndexedData.push(this.currentIndexedData.length); this._sourceLength += 1; - + this.initTreeHierarchyState(); if (this.userPagination) { //如果用户配置了分页 this.pagination.totalCount = this._sourceLength; @@ -836,7 +843,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { ); } this.currentIndexedData = sortedIndexArray; - if (this.enableHierarchyState) { + if (this.hierarchyExpandLevel) { let nodeLength = sortedIndexArray.length; const t0 = window.performance.now(); for (let i = 0; i < nodeLength; i++) { diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index 32e2220ab..934b4dc9f 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -1462,7 +1462,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { getRecordStartRowByRecordIndex(index: number): number { return this.columnHeaderLevelCount + index; } - getRecordIndexByCell(col: number, row: number): number { + getRecordShowIndexByCell(col: number, row: number): number { return undefined; } // getCellRangeTranspose(): CellRange { diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 673051670..6182d94f6 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -793,7 +793,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { range1.end.row === range2.end.row ); } - getRecordIndexByCell(col: number, row: number): number { + getRecordShowIndexByCell(col: number, row: number): number { const skipRowCount = this.hasAggregationOnTopCount ? this.headerLevelCount + 1 : this.headerLevelCount; if (this.transpose) { if (col < skipRowCount) { diff --git a/packages/vtable/src/ts-types/list-table/layout-map/api.ts b/packages/vtable/src/ts-types/list-table/layout-map/api.ts index a42c04911..8131452da 100644 --- a/packages/vtable/src/ts-types/list-table/layout-map/api.ts +++ b/packages/vtable/src/ts-types/list-table/layout-map/api.ts @@ -237,7 +237,7 @@ interface LayoutMapAPI { // getBodyLayoutRangeById: (id: LayoutObjectId) => CellRange; getHeaderCellAdressById: (id: number) => CellAddress | undefined; getHeaderCellAddressByField: (field: string) => CellAddress | undefined; - getRecordIndexByCell: (col: number, row: number) => number; + getRecordShowIndexByCell: (col: number, row: number) => number; getRecordStartRowByRecordIndex: (index: number) => number; /** 从定义中获取一列配置项width的定义值 */ getColumnWidthDefined: (col: number) => WidthData; From 60418ecd7b5f07468681e3910261dbdebb9f711f Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 21 Feb 2024 20:24:32 +0800 Subject: [PATCH 30/63] docs: update changlog of rush --- ...es-records-api-for-tree-mode_2024-02-21-12-24.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1121-feature-list-tables-records-api-for-tree-mode_2024-02-21-12-24.json diff --git a/common/changes/@visactor/vtable/1121-feature-list-tables-records-api-for-tree-mode_2024-02-21-12-24.json b/common/changes/@visactor/vtable/1121-feature-list-tables-records-api-for-tree-mode_2024-02-21-12-24.json new file mode 100644 index 000000000..ff79fbab5 --- /dev/null +++ b/common/changes/@visactor/vtable/1121-feature-list-tables-records-api-for-tree-mode_2024-02-21-12-24.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "feat: add api getRecordIndexByCell #1121\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 88670448cda6be7e613296150660f83b6ac427d3 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 21 Feb 2024 20:36:02 +0800 Subject: [PATCH 31/63] feat: add api getRecordIndexByCell #1121 --- packages/vtable/src/core/tableHelper.ts | 2 +- packages/vtable/src/ts-types/table-engine.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vtable/src/core/tableHelper.ts b/packages/vtable/src/core/tableHelper.ts index 86b141a63..b534bb3e6 100644 --- a/packages/vtable/src/core/tableHelper.ts +++ b/packages/vtable/src/core/tableHelper.ts @@ -43,7 +43,7 @@ export function _dealWithUpdateDataSource(table: BaseTableAPI, fn: (table: BaseT table.internalProps.dataSourceEventIds = [ table.internalProps.handler.on(table.internalProps.dataSource, DataSource.EVENT_TYPE.CHANGE_ORDER, () => { - if (table.dataSource.enableHierarchyState) { + if (table.dataSource.hierarchyExpandLevel) { table.refreshRowColCount(); } table.render(); diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index 6e2fe1695..1b0a10093 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -104,7 +104,7 @@ export interface DataSourceAPI { updatePagination: (pagination: IPagination) => void; getIndexKey: (index: number) => number | number[]; /** 数据是否为树形结构 且可以展开收起 */ - enableHierarchyState: boolean; + hierarchyExpandLevel: number; } export interface SortState { From e0978a86df707c9ef649eed6a8340f0af4ac4678 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 23 Feb 2024 11:49:05 +0800 Subject: [PATCH 32/63] fix: when scroll tooltip hide #905 --- .../src/components/tooltip/TooltipHandler.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/vtable/src/components/tooltip/TooltipHandler.ts b/packages/vtable/src/components/tooltip/TooltipHandler.ts index a21d32a45..d8a99022d 100644 --- a/packages/vtable/src/components/tooltip/TooltipHandler.ts +++ b/packages/vtable/src/components/tooltip/TooltipHandler.ts @@ -204,13 +204,14 @@ export class TooltipHandler { this._unbindFromCell(); }); table.on(TABLE_EVENT_TYPE.SCROLL, e => { - const info = this._attachInfo; - if (info?.tooltipOptions && info?.range?.start) { - const { col, row } = info.range.start; - const rect = table.getCellRangeRelativeRect({ col, row }); - info.tooltipOptions.referencePosition.rect = rect; - this._move(info.range.start.col, info.range.start.row, info.tooltipOptions); - } + this._unbindFromCell(); + // const info = this._attachInfo; + // if (info?.tooltipOptions && info?.range?.start) { + // const { col, row } = info.range.start; + // const rect = table.getCellRangeRelativeRect({ col, row }); + // info.tooltipOptions.referencePosition.rect = rect; + // this._move(info.range.start.col, info.range.start.row, info.tooltipOptions); + // } }); } _getTooltipInstanceInfo(col: number, row: number): BaseTooltip | null { From 8eee15f99e785c90cf74d691ca0fed9f1291e817 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 23 Feb 2024 11:49:26 +0800 Subject: [PATCH 33/63] docs: update changlog of rush --- .../vtable/905-bug-tooltip-hide_2024-02-23-03-49.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/905-bug-tooltip-hide_2024-02-23-03-49.json diff --git a/common/changes/@visactor/vtable/905-bug-tooltip-hide_2024-02-23-03-49.json b/common/changes/@visactor/vtable/905-bug-tooltip-hide_2024-02-23-03-49.json new file mode 100644 index 000000000..7d742548e --- /dev/null +++ b/common/changes/@visactor/vtable/905-bug-tooltip-hide_2024-02-23-03-49.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: when scroll tooltip hide #905\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From e231aea644dfb2ee3324b57efa2e1098b661ff71 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 8 Feb 2024 12:03:03 +0800 Subject: [PATCH 34/63] fix: fix content position update problem --- .../vtable/fix-merge-cell-update_2024-02-08-04-02.json | 10 ++++++++++ .../vtable/src/scenegraph/group-creater/cell-helper.ts | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 common/changes/@visactor/vtable/fix-merge-cell-update_2024-02-08-04-02.json diff --git a/common/changes/@visactor/vtable/fix-merge-cell-update_2024-02-08-04-02.json b/common/changes/@visactor/vtable/fix-merge-cell-update_2024-02-08-04-02.json new file mode 100644 index 000000000..8fb383e10 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-merge-cell-update_2024-02-08-04-02.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix: fix content position update problem", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index 3072fa729..3e5044f37 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -469,9 +469,9 @@ export function updateCell(col: number, row: number, table: BaseTableAPI, addNew dx: hierarchyOffset, x }; - const oldText = textMark.attribute.text; + // const oldText = textMark.attribute.text; textMark.setAttributes(cellTheme.text ? (Object.assign({}, cellTheme.text, attribute) as any) : attribute); - if (!oldText && textMark.attribute.text) { + if (textMark.attribute.text) { const textBaseline = cellTheme.text.textBaseline; const height = cellHeight - (padding[0] + padding[2]); let y = 0; From 9db5101532bc9eb608a4d2b3086dd199e95d2d8c Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 8 Feb 2024 15:39:26 +0800 Subject: [PATCH 35/63] fix: update merge cell size in updateCell() --- .../scenegraph/group-creater/cell-helper.ts | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index 3e5044f37..9f4c7f716 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -35,6 +35,7 @@ import { resizeCellGroup } from './column-helper'; import { getHierarchyOffset } from '../utils/get-hierarchy-offset'; import { getQuadProps } from '../utils/padding'; import { convertInternal } from '../../tools/util'; +import { updateCellContentHeight, updateCellContentWidth } from '../utils/text-icon-layout'; export function createCell( type: ColumnTypeOption, @@ -585,15 +586,57 @@ export function updateCell(col: number, row: number, table: BaseTableAPI, addNew } if (isMerge) { - const rangeHeight = table.getRowHeight(row); - const rangeWidth = table.getColWidth(col); + // const rangeHeight = table.getRowHeight(row); + // const rangeWidth = table.getColWidth(col); const { width: contentWidth } = newCellGroup.attribute; const { height: contentHeight } = newCellGroup.attribute; newCellGroup.contentWidth = contentWidth; newCellGroup.contentHeight = contentHeight; - resizeCellGroup(newCellGroup, rangeWidth, rangeHeight, range, table); + // resizeCellGroup(newCellGroup, rangeWidth, rangeHeight, range, table); + for (let col = range.start.col; col <= range.end.col; col++) { + for (let row = range.start.row; row <= range.end.row; row++) { + const cellGroup = table.scenegraph.getCell(col, row, true); + + if (range.start.row !== range.end.row) { + // const cellGroup = table.scenegraph.getCell(col, row, true); + updateCellContentHeight( + cellGroup, + cellHeight, + cellHeight, + table.heightMode === 'autoHeight', + padding, + textAlign, + textBaseline + ); + } + if (range.start.col !== range.end.col) { + // const cellGroup = table.scenegraph.getCell(col, row, true); + updateCellContentWidth( + cellGroup, + cellWidth, + cellHeight, + 0, + table.heightMode === 'autoHeight', + padding, + textAlign, + textBaseline, + table.scenegraph + ); + } + // TODO: deal width custom merge + // ... + + newCellGroup.contentWidth = cellWidth; + newCellGroup.contentHeight = cellHeight; + + const rangeHeight = table.getRowHeight(row); + const rangeWidth = table.getColWidth(col); + + resizeCellGroup(cellGroup, rangeWidth, rangeHeight, range, table); + } + } } return newCellGroup; @@ -664,6 +707,7 @@ function updateCellContent( } function canUseFastUpdate(col: number, row: number, oldCellGroup: Group, autoWrapText: boolean, table: BaseTableAPI) { + // return false; const define = table.getBodyColumnDefine(col, row); const mayHaveIcon = !!define?.icon || !!define?.tree; const cellType = table.getBodyColumnType(col, row); From 8aed1d1237b9998094942336578137853cf7a8d9 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Sun, 18 Feb 2024 18:44:47 +0800 Subject: [PATCH 36/63] fix: fix merge cell style update --- .../vtable/src/scenegraph/group-creater/cell-helper.ts | 9 +++++---- .../src/scenegraph/group-creater/column-helper.ts | 10 ++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index 9f4c7f716..97c4aeff8 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -501,8 +501,8 @@ export function updateCell(col: number, row: number, table: BaseTableAPI, addNew const mayHaveIcon = cellLocation !== 'body' ? true : !!define?.icon || !!define?.tree; const padding = cellTheme._vtable.padding; - const textAlign = cellTheme._vtable.textAlign; - const textBaseline = cellTheme._vtable.textBaseline; + const textAlign = cellTheme.text.textAlign; + const textBaseline = cellTheme.text.textBaseline; let newCellGroup; let bgColorFunc: Function; @@ -609,6 +609,7 @@ export function updateCell(col: number, row: number, table: BaseTableAPI, addNew padding, textAlign, textBaseline + // 'middle' ); } if (range.start.col !== range.end.col) { @@ -628,8 +629,8 @@ export function updateCell(col: number, row: number, table: BaseTableAPI, addNew // TODO: deal width custom merge // ... - newCellGroup.contentWidth = cellWidth; - newCellGroup.contentHeight = cellHeight; + cellGroup.contentWidth = cellWidth; + cellGroup.contentHeight = cellHeight; const rangeHeight = table.getRowHeight(row); const rangeWidth = table.getColWidth(col); diff --git a/packages/vtable/src/scenegraph/group-creater/column-helper.ts b/packages/vtable/src/scenegraph/group-creater/column-helper.ts index 5ca12604d..8ab56f01a 100644 --- a/packages/vtable/src/scenegraph/group-creater/column-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/column-helper.ts @@ -80,7 +80,8 @@ export function createComplexColumn( range = customMergeRange; isMerge = range.start.col !== range.end.col || range.start.row !== range.end.row; if (isMerge) { - const mergeSize = dealMerge(range, mergeMap, table); + const needUpdateRange = rowStart > range.start.row; + const mergeSize = dealMerge(range, mergeMap, table, needUpdateRange); cellWidth = mergeSize.cellWidth; cellHeight = mergeSize.cellHeight; } @@ -122,7 +123,8 @@ export function createComplexColumn( isMerge = range.start.col !== range.end.col || range.start.row !== range.end.row; // 所有Merge单元格,只保留左上角一个真实的单元格,其他使用空Group占位 if (isMerge) { - const mergeSize = dealMerge(range, mergeMap, table); + const needUpdateRange = rowStart > range.start.row; + const mergeSize = dealMerge(range, mergeMap, table, needUpdateRange); cellWidth = mergeSize.cellWidth; cellHeight = mergeSize.cellHeight; } @@ -323,11 +325,11 @@ export function resizeCellGroup( }; } -function dealMerge(range: CellRange, mergeMap: MergeMap, table: BaseTableAPI) { +function dealMerge(range: CellRange, mergeMap: MergeMap, table: BaseTableAPI, forceUpdate: boolean) { let cellWidth = 0; let cellHeight = 0; const mergeResult = mergeMap.get(`${range.start.col},${range.start.row};${range.end.col},${range.end.row}`); - if (!mergeResult) { + if (!mergeResult || forceUpdate) { for (let col = range.start.col; col <= range.end.col; col++) { cellWidth += table.getColWidth(col); } From 19809fe6662bd2623c9dffebb697aaecf5b4f3e6 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 19 Feb 2024 10:53:50 +0800 Subject: [PATCH 37/63] feat: add dealWithMergeCellSize() --- .../scenegraph/group-creater/cell-helper.ts | 106 ++++++++++-------- 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index 97c4aeff8..db6fa6b9c 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -594,50 +594,7 @@ export function updateCell(col: number, row: number, table: BaseTableAPI, addNew newCellGroup.contentWidth = contentWidth; newCellGroup.contentHeight = contentHeight; - // resizeCellGroup(newCellGroup, rangeWidth, rangeHeight, range, table); - for (let col = range.start.col; col <= range.end.col; col++) { - for (let row = range.start.row; row <= range.end.row; row++) { - const cellGroup = table.scenegraph.getCell(col, row, true); - - if (range.start.row !== range.end.row) { - // const cellGroup = table.scenegraph.getCell(col, row, true); - updateCellContentHeight( - cellGroup, - cellHeight, - cellHeight, - table.heightMode === 'autoHeight', - padding, - textAlign, - textBaseline - // 'middle' - ); - } - if (range.start.col !== range.end.col) { - // const cellGroup = table.scenegraph.getCell(col, row, true); - updateCellContentWidth( - cellGroup, - cellWidth, - cellHeight, - 0, - table.heightMode === 'autoHeight', - padding, - textAlign, - textBaseline, - table.scenegraph - ); - } - // TODO: deal width custom merge - // ... - - cellGroup.contentWidth = cellWidth; - cellGroup.contentHeight = cellHeight; - - const rangeHeight = table.getRowHeight(row); - const rangeWidth = table.getColWidth(col); - - resizeCellGroup(cellGroup, rangeWidth, rangeHeight, range, table); - } - } + dealWithMergeCellSize(range, cellWidth, cellHeight, padding, textAlign, textBaseline, table); } return newCellGroup; @@ -729,3 +686,64 @@ function canUseFastUpdate(col: number, row: number, oldCellGroup: Group, autoWra } return false; } + +function dealWithMergeCellSize( + range: CellRange, + cellWidth: number, + cellHeight: number, + padding: [number, number, number, number], + textAlign: CanvasTextAlign, + textBaseline: CanvasTextBaseline, + table: BaseTableAPI +) { + // const rangeHeight = table.getRowHeight(row); + // const rangeWidth = table.getColWidth(col); + + // const { width: contentWidth } = newCellGroup.attribute; + // const { height: contentHeight } = newCellGroup.attribute; + // newCellGroup.contentWidth = contentWidth; + // newCellGroup.contentHeight = contentHeight; + + // resizeCellGroup(newCellGroup, rangeWidth, rangeHeight, range, table); + for (let col = range.start.col; col <= range.end.col; col++) { + for (let row = range.start.row; row <= range.end.row; row++) { + const cellGroup = table.scenegraph.getCell(col, row, true); + + if (range.start.row !== range.end.row) { + // const cellGroup = table.scenegraph.getCell(col, row, true); + updateCellContentHeight( + cellGroup, + cellHeight, + cellHeight, + table.heightMode === 'autoHeight', + padding, + textAlign, + textBaseline + // 'middle' + ); + } + if (range.start.col !== range.end.col) { + // const cellGroup = table.scenegraph.getCell(col, row, true); + updateCellContentWidth( + cellGroup, + cellWidth, + cellHeight, + 0, + table.heightMode === 'autoHeight', + padding, + textAlign, + textBaseline, + table.scenegraph + ); + } + + cellGroup.contentWidth = cellWidth; + cellGroup.contentHeight = cellHeight; + + const rangeHeight = table.getRowHeight(row); + const rangeWidth = table.getColWidth(col); + + resizeCellGroup(cellGroup, rangeWidth, rangeHeight, range, table); + } + } +} From d61c4d052eba29a26197e0d0de98885b0cfff19f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 23 Feb 2024 10:10:15 +0000 Subject: [PATCH 38/63] build: prelease version 0.20.0 --- ...e-listtable-caculate_2024-02-04-11-47.json | 11 ----- ...e-listtable-caculate_2024-02-07-11-56.json | 11 ----- ...e-listtable-caculate_2024-02-08-09-51.json | 11 ----- ...ug-selectBoderExtend_2024-02-07-09-29.json | 11 ----- ...useleavecell-trigger_2024-02-20-07-59.json | 11 ----- ...pec-support-function_2024-02-20-11-59.json | 11 ----- ...7-feature-filter-api_2024-01-31-12-05.json | 11 ----- .../fix-cellBgColor_2024-02-22-09-01.json | 10 ----- ...-custom-merge-height_2024-02-06-08-19.json | 10 ----- ...ix-merge-cell-update_2024-02-08-04-02.json | 10 ----- ...ropDownMenuHighlight_2024-02-20-10-09.json | 10 ----- .../fix-react-strict_2024-02-06-09-17.json | 10 ----- common/config/rush/version-policies.json | 2 +- packages/react-vtable/package.json | 2 +- packages/vtable-editors/package.json | 2 +- packages/vtable-export/package.json | 2 +- packages/vtable/CHANGELOG.json | 45 +++++++++++++++++++ packages/vtable/CHANGELOG.md | 34 +++++++++++++- packages/vtable/package.json | 2 +- 19 files changed, 83 insertions(+), 133 deletions(-) delete mode 100644 common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-04-11-47.json delete mode 100644 common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-07-11-56.json delete mode 100644 common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-08-09-51.json delete mode 100644 common/changes/@visactor/vtable/1091-bug-selectBoderExtend_2024-02-07-09-29.json delete mode 100644 common/changes/@visactor/vtable/1112-bug-onmouseleavecell-trigger_2024-02-20-07-59.json delete mode 100644 common/changes/@visactor/vtable/1115-feature-chartspec-support-function_2024-02-20-11-59.json delete mode 100644 common/changes/@visactor/vtable/607-feature-filter-api_2024-01-31-12-05.json delete mode 100644 common/changes/@visactor/vtable/fix-cellBgColor_2024-02-22-09-01.json delete mode 100644 common/changes/@visactor/vtable/fix-custom-merge-height_2024-02-06-08-19.json delete mode 100644 common/changes/@visactor/vtable/fix-merge-cell-update_2024-02-08-04-02.json delete mode 100644 common/changes/@visactor/vtable/fix-merge-setDropDownMenuHighlight_2024-02-20-10-09.json delete mode 100644 common/changes/@visactor/vtable/fix-react-strict_2024-02-06-09-17.json diff --git a/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-04-11-47.json b/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-04-11-47.json deleted file mode 100644 index da4cedf8c..000000000 --- a/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-04-11-47.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "feat: add aggregation for list table column\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-07-11-56.json b/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-07-11-56.json deleted file mode 100644 index 1627da211..000000000 --- a/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-07-11-56.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "feat: add api getAggregateValuesByField\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-08-09-51.json b/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-08-09-51.json deleted file mode 100644 index 40e03b3df..000000000 --- a/common/changes/@visactor/vtable/1038-feature-listtable-caculate_2024-02-08-09-51.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "feat: add custom aggregation\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1091-bug-selectBoderExtend_2024-02-07-09-29.json b/common/changes/@visactor/vtable/1091-bug-selectBoderExtend_2024-02-07-09-29.json deleted file mode 100644 index a20dd8c11..000000000 --- a/common/changes/@visactor/vtable/1091-bug-selectBoderExtend_2024-02-07-09-29.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "fix: edit right frozen cell input position error\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1112-bug-onmouseleavecell-trigger_2024-02-20-07-59.json b/common/changes/@visactor/vtable/1112-bug-onmouseleavecell-trigger_2024-02-20-07-59.json deleted file mode 100644 index baeda1440..000000000 --- a/common/changes/@visactor/vtable/1112-bug-onmouseleavecell-trigger_2024-02-20-07-59.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "fix: mouseleave_cell event trigger #1112\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1115-feature-chartspec-support-function_2024-02-20-11-59.json b/common/changes/@visactor/vtable/1115-feature-chartspec-support-function_2024-02-20-11-59.json deleted file mode 100644 index dc0457eaf..000000000 --- a/common/changes/@visactor/vtable/1115-feature-chartspec-support-function_2024-02-20-11-59.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "feat: chartSpec support function #1115\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/607-feature-filter-api_2024-01-31-12-05.json b/common/changes/@visactor/vtable/607-feature-filter-api_2024-01-31-12-05.json deleted file mode 100644 index c5391b829..000000000 --- a/common/changes/@visactor/vtable/607-feature-filter-api_2024-01-31-12-05.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "feat: add filter data config #607\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-cellBgColor_2024-02-22-09-01.json b/common/changes/@visactor/vtable/fix-cellBgColor_2024-02-22-09-01.json deleted file mode 100644 index 31b0a9a74..000000000 --- a/common/changes/@visactor/vtable/fix-cellBgColor_2024-02-22-09-01.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: fix cellBgColor judgement in isCellHover()", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-custom-merge-height_2024-02-06-08-19.json b/common/changes/@visactor/vtable/fix-custom-merge-height_2024-02-06-08-19.json deleted file mode 100644 index 8d5109f06..000000000 --- a/common/changes/@visactor/vtable/fix-custom-merge-height_2024-02-06-08-19.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: fix custom merge cell computed height&width", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-merge-cell-update_2024-02-08-04-02.json b/common/changes/@visactor/vtable/fix-merge-cell-update_2024-02-08-04-02.json deleted file mode 100644 index 8fb383e10..000000000 --- a/common/changes/@visactor/vtable/fix-merge-cell-update_2024-02-08-04-02.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: fix content position update problem", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-merge-setDropDownMenuHighlight_2024-02-20-10-09.json b/common/changes/@visactor/vtable/fix-merge-setDropDownMenuHighlight_2024-02-20-10-09.json deleted file mode 100644 index e9147d3f2..000000000 --- a/common/changes/@visactor/vtable/fix-merge-setDropDownMenuHighlight_2024-02-20-10-09.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: merge cell update in setDropDownMenuHighlight()", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-react-strict_2024-02-06-09-17.json b/common/changes/@visactor/vtable/fix-react-strict_2024-02-06-09-17.json deleted file mode 100644 index 6f30fc4d6..000000000 --- a/common/changes/@visactor/vtable/fix-react-strict_2024-02-06-09-17.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: fix react-vtable display error in react strict mode #990", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index ae5a028b3..f2e90f606 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -1 +1 @@ -[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"0.19.1","mainProject":"@visactor/vtable","nextBump":"patch"}] +[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"0.20.0","mainProject":"@visactor/vtable","nextBump":"minor"}] diff --git a/packages/react-vtable/package.json b/packages/react-vtable/package.json index 12ea3bd6d..cc27be3b3 100644 --- a/packages/react-vtable/package.json +++ b/packages/react-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vtable", - "version": "0.19.1", + "version": "0.20.0", "description": "The react version of VTable", "keywords": [ "react", diff --git a/packages/vtable-editors/package.json b/packages/vtable-editors/package.json index a09624fef..e34714b92 100644 --- a/packages/vtable-editors/package.json +++ b/packages/vtable-editors/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-editors", - "version": "0.19.1", + "version": "0.20.0", "description": "", "sideEffects": false, "main": "cjs/index.js", diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index 99c224180..dfb34ebce 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-export", - "version": "0.19.1", + "version": "0.20.0", "description": "The export util of VTable", "author": { "name": "VisActor", diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index 42899acb0..c7dc6eb03 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -1,6 +1,51 @@ { "name": "@visactor/vtable", "entries": [ + { + "version": "0.20.0", + "tag": "@visactor/vtable_v0.20.0", + "date": "Fri, 23 Feb 2024 10:06:24 GMT", + "comments": { + "none": [ + { + "comment": "feat: add aggregation for list table column\n\n" + }, + { + "comment": "feat: add api getAggregateValuesByField\n\n" + }, + { + "comment": "feat: add custom aggregation\n\n" + }, + { + "comment": "fix: edit right frozen cell input position error\n\n" + }, + { + "comment": "fix: mouseleave_cell event trigger #1112\n\n" + }, + { + "comment": "feat: chartSpec support function #1115\n\n" + }, + { + "comment": "feat: add filter data config #607\n\n" + }, + { + "comment": "fix: fix cellBgColor judgement in isCellHover()" + }, + { + "comment": "fix: fix custom merge cell computed height&width" + }, + { + "comment": "fix: fix content position update problem" + }, + { + "comment": "fix: merge cell update in setDropDownMenuHighlight()" + }, + { + "comment": "fix: fix react-vtable display error in react strict mode #990" + } + ] + } + }, { "version": "0.19.1", "tag": "@visactor/vtable_v0.19.1", diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index dbad8447a..f600d2d7f 100644 --- a/packages/vtable/CHANGELOG.md +++ b/packages/vtable/CHANGELOG.md @@ -1,6 +1,38 @@ # Change Log - @visactor/vtable -This log was last generated on Mon, 05 Feb 2024 12:36:17 GMT and should not be manually modified. +This log was last generated on Fri, 23 Feb 2024 10:06:24 GMT and should not be manually modified. + +## 0.20.0 +Fri, 23 Feb 2024 10:06:24 GMT + +### Updates + +- feat: add aggregation for list table column + + +- feat: add api getAggregateValuesByField + + +- feat: add custom aggregation + + +- fix: edit right frozen cell input position error + + +- fix: mouseleave_cell event trigger #1112 + + +- feat: chartSpec support function #1115 + + +- feat: add filter data config #607 + + +- fix: fix cellBgColor judgement in isCellHover() +- fix: fix custom merge cell computed height&width +- fix: fix content position update problem +- fix: merge cell update in setDropDownMenuHighlight() +- fix: fix react-vtable display error in react strict mode #990 ## 0.19.1 Mon, 05 Feb 2024 12:36:17 GMT diff --git a/packages/vtable/package.json b/packages/vtable/package.json index 13febc158..558d753cf 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable", - "version": "0.19.1", + "version": "0.20.0", "description": "canvas table width high performance", "keywords": [ "grid", From 4e826475ffe6a2b7e355467283d9d8ce2fd0798b Mon Sep 17 00:00:00 2001 From: fangsmile Date: Fri, 23 Feb 2024 12:15:41 +0000 Subject: [PATCH 39/63] docs: generate changelog of release v0.20.0 --- docs/assets/changelog/en/release.md | 27 +++++++++++++++++++++++++++ docs/assets/changelog/zh/release.md | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/docs/assets/changelog/en/release.md b/docs/assets/changelog/en/release.md index c2000a7d9..e3a163d5b 100644 --- a/docs/assets/changelog/en/release.md +++ b/docs/assets/changelog/en/release.md @@ -1,3 +1,30 @@ +# v0.20.0 + +2024-02-23 + + +**🆕 New feature** + +- **@visactor/vtable**: add aggregation for list table column +- **@visactor/vtable**: add api getAggregateValuesByField +- **@visactor/vtable**: add custom aggregation +- **@visactor/vtable**: chartSpec support function [#1115](https://github.com/VisActor/VTable/issues/1115) +- **@visactor/vtable**: add filter data config [#607](https://github.com/VisActor/VTable/issues/607) + +**🐛 Bug fix** + +- **@visactor/vtable**: edit right frozen cell input position error +- **@visactor/vtable**: mouseleave_cell event trigger [#1112](https://github.com/VisActor/VTable/issues/1112) +- **@visactor/vtable**: fix cellBgColor judgement in isCellHover() +- **@visactor/vtable**: fix custom merge cell computed height&width +- **@visactor/vtable**: fix content position update problem +- **@visactor/vtable**: merge cell update in setDropDownMenuHighlight() +- **@visactor/vtable**: fix react-vtable display error in react strict mode [#990](https://github.com/VisActor/VTable/issues/990) + + + +[more detail about v0.20.0](https://github.com/VisActor/VTable/releases/tag/v0.20.0) + # v0.19.1 2024-02-06 diff --git a/docs/assets/changelog/zh/release.md b/docs/assets/changelog/zh/release.md index 724d438c7..51d07b36a 100644 --- a/docs/assets/changelog/zh/release.md +++ b/docs/assets/changelog/zh/release.md @@ -1,3 +1,30 @@ +# v0.20.0 + +2024-02-23 + + +**🆕 新增功能** + +- **@visactor/vtable**: add aggregation for list table column +- **@visactor/vtable**: add api getAggregateValuesByField +- **@visactor/vtable**: add custom aggregation +- **@visactor/vtable**: chartSpec support function [#1115](https://github.com/VisActor/VTable/issues/1115) +- **@visactor/vtable**: add filter data config [#607](https://github.com/VisActor/VTable/issues/607) + +**🐛 功能修复** + +- **@visactor/vtable**: edit right frozen cell input position error +- **@visactor/vtable**: mouseleave_cell event trigger [#1112](https://github.com/VisActor/VTable/issues/1112) +- **@visactor/vtable**: fix cellBgColor judgement in isCellHover() +- **@visactor/vtable**: fix custom merge cell computed height&width +- **@visactor/vtable**: fix content position update problem +- **@visactor/vtable**: merge cell update in setDropDownMenuHighlight() +- **@visactor/vtable**: fix react-vtable display error in react strict mode [#990](https://github.com/VisActor/VTable/issues/990) + + + +[更多详情请查看 v0.20.0](https://github.com/VisActor/VTable/releases/tag/v0.20.0) + # v0.19.1 2024-02-06 From 020bd4425bdaf267ec6b76e05611f153ca6d6a2f Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 23 Feb 2024 20:47:28 +0800 Subject: [PATCH 40/63] docs: add 0.20.0 release change log --- docs/assets/changelog/zh/release.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/assets/changelog/zh/release.md b/docs/assets/changelog/zh/release.md index 51d07b36a..eb030e069 100644 --- a/docs/assets/changelog/zh/release.md +++ b/docs/assets/changelog/zh/release.md @@ -4,23 +4,22 @@ **🆕 新增功能** - -- **@visactor/vtable**: add aggregation for list table column -- **@visactor/vtable**: add api getAggregateValuesByField -- **@visactor/vtable**: add custom aggregation -- **@visactor/vtable**: chartSpec support function [#1115](https://github.com/VisActor/VTable/issues/1115) -- **@visactor/vtable**: add filter data config [#607](https://github.com/VisActor/VTable/issues/607) + +- **@visactor/vtable**:添加列表列的聚合 +- **@visactor/vtable**:添加 api getAggregateValuesByField +- **@visactor/vtable**:添加自定义聚合 +- **@visactor/vtable**:chartSpec 支持函数 [#1115](https://github.com/VisActor/VTable/issues/1115) +- **@visactor/vtable**:添加基本表格的过滤能力 [#607](https://github.com/VisActor/VTable/issues/607) **🐛 功能修复** - -- **@visactor/vtable**: edit right frozen cell input position error -- **@visactor/vtable**: mouseleave_cell event trigger [#1112](https://github.com/VisActor/VTable/issues/1112) -- **@visactor/vtable**: fix cellBgColor judgement in isCellHover() -- **@visactor/vtable**: fix custom merge cell computed height&width -- **@visactor/vtable**: fix content position update problem -- **@visactor/vtable**: merge cell update in setDropDownMenuHighlight() -- **@visactor/vtable**: fix react-vtable display error in react strict mode [#990](https://github.com/VisActor/VTable/issues/990) - + +- **@visactor/vtable**:编辑右冻结单元格输入位置错误 +- **@visactor/vtable**:mouseleave_cell 事件触发器 [#1112](https://github.com/VisActor/VTable/issues/1112) +- **@visactor/vtable**:修复 isCellHover() 中的 cellBgColor 判断 +- **@visactor/vtable**:修复自定义合并单元计算的高度和宽度 +- **@visactor/vtable**:修复内容位置更新问题 +- **@visactor/vtable**:在 setDropDownMenuHighlight() 中合并单元格更新 +- **@visactor/vtable**:修复react严格模式下的react-vtable显示错误[#990](https://github.com/VisActor/VTable/issues/990) [更多详情请查看 v0.20.0](https://github.com/VisActor/VTable/releases/tag/v0.20.0) From b0063dd163dfb52a73b16bb31cd90d20cda15854 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Sun, 25 Feb 2024 21:41:35 +0800 Subject: [PATCH 41/63] fix: hideColumnsSubheader with three levels show error #1105 --- packages/vtable/src/layout/simple-header-layout.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index ffecfe47c..5ce9943ea 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -674,15 +674,17 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { results[id] = cell; for (let r = row - 1; r >= 0; r--) { - this._headerCellIds[r][col] = roots[r]; + this._headerCellIds[r] && (this._headerCellIds[r][col] = roots[r]); } if (!hideColumnsSubHeader) { rowCells[col] = id; - } else { + } else if (this._headerCellIds[row - 1]) { rowCells[col] = this._headerCellIds[row - 1][col]; } if (hd.columns) { - this._addHeaders(row + 1, hd.columns, [...roots, id], hd.hideColumnsSubHeader).forEach(c => results.push(c)); + this._addHeaders(row + 1, hd.columns, [...roots, id], hd.hideColumnsSubHeader || hideColumnsSubHeader).forEach( + c => results.push(c) + ); } else { const colDef = hd; this._columns.push({ From e3d5a2c9480089e553ac624b9f27a826e6180a82 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Sun, 25 Feb 2024 21:42:10 +0800 Subject: [PATCH 42/63] docs: update changlog of rush --- ...g-hidecolumnssubheader-error_2024-02-25-13-42.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1105-bug-hidecolumnssubheader-error_2024-02-25-13-42.json diff --git a/common/changes/@visactor/vtable/1105-bug-hidecolumnssubheader-error_2024-02-25-13-42.json b/common/changes/@visactor/vtable/1105-bug-hidecolumnssubheader-error_2024-02-25-13-42.json new file mode 100644 index 000000000..799799e88 --- /dev/null +++ b/common/changes/@visactor/vtable/1105-bug-hidecolumnssubheader-error_2024-02-25-13-42.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: hideColumnsSubheader with three levels show error #1105\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 240781fef4a3c8f95071bc39d694636b01b15e93 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 26 Feb 2024 12:22:01 +0800 Subject: [PATCH 43/63] refactor: add event to dropdown event #1129 --- packages/vtable/src/ListTable.ts | 3 ++- packages/vtable/src/PivotChart.ts | 3 ++- packages/vtable/src/PivotTable.ts | 3 ++- .../src/components/menu/dom/logic/MenuContainer.ts | 3 ++- .../vtable/src/components/menu/dom/logic/MenuElement.ts | 9 ++++++--- packages/vtable/src/scenegraph/component/menu.ts | 1 + packages/vtable/src/ts-types/menu.ts | 1 + 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 645e67d80..461959eb4 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -744,7 +744,8 @@ export class ListTable extends BaseTable implements ListTableAPI { const result: DropDownMenuEventInfo = { field: this.getHeaderField(col, row), value: this.getCellValue(col, row), - cellLocation: this.getCellLocation(col, row) + cellLocation: this.getCellLocation(col, row), + event: undefined }; return result; } diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index 687264418..d7693c12b 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -722,7 +722,8 @@ export class PivotChart extends BaseTable implements PivotChartAPI { dimensionKey: dimensionInfos[dimensionInfos.length - 1].dimensionKey, value: this.getCellValue(col, row), cellLocation: this.getCellLocation(col, row), - isPivotCorner: this.isCornerHeader(col, row) + isPivotCorner: this.isCornerHeader(col, row), + event: undefined }; return result; } diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index bc70f12aa..74f4f912f 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -995,7 +995,8 @@ export class PivotTable extends BaseTable implements PivotTableAPI { dimensionKey: dimensionInfos[dimensionInfos.length - 1].dimensionKey, value: this.getCellValue(col, row), cellLocation: this.getCellLocation(col, row), - isPivotCorner: this.isCornerHeader(col, row) + isPivotCorner: this.isCornerHeader(col, row), + event: undefined }; return result; } diff --git a/packages/vtable/src/components/menu/dom/logic/MenuContainer.ts b/packages/vtable/src/components/menu/dom/logic/MenuContainer.ts index 62306bbee..b7b152d88 100644 --- a/packages/vtable/src/components/menu/dom/logic/MenuContainer.ts +++ b/packages/vtable/src/components/menu/dom/logic/MenuContainer.ts @@ -59,7 +59,8 @@ export class MenuContainer { // dropDownIndex, text, highlight, - cellLocation: table.getCellLocation(col, row) + cellLocation: table.getCellLocation(col, row), + event: e }); table.fireListeners(TABLE_EVENT_TYPE.DROPDOWN_MENU_CLEAR, null); // 清除菜单 diff --git a/packages/vtable/src/components/menu/dom/logic/MenuElement.ts b/packages/vtable/src/components/menu/dom/logic/MenuElement.ts index 6e82511c2..682c78101 100644 --- a/packages/vtable/src/components/menu/dom/logic/MenuElement.ts +++ b/packages/vtable/src/components/menu/dom/logic/MenuElement.ts @@ -139,7 +139,8 @@ export class MenuElement { // dropDownIndex, text, highlight, - cellLocation: table.getCellLocation(col, row) + cellLocation: table.getCellLocation(col, row), + event: e }); table.fireListeners(TABLE_EVENT_TYPE.DROPDOWN_MENU_CLEAR, null); // 清除菜单 @@ -175,7 +176,8 @@ export class MenuElement { // dropDownIndex, text, highlight, - cellLocation: table.getCellLocation(col, row) + cellLocation: table.getCellLocation(col, row), + event: e }); table.fireListeners(TABLE_EVENT_TYPE.DROPDOWN_MENU_CLEAR, null); // 清除菜单 @@ -321,7 +323,8 @@ export class MenuElement { menuKey, text, highlight, - cellLocation: table.getCellLocation(col, row) + cellLocation: table.getCellLocation(col, row), + event: e }); table.fireListeners(TABLE_EVENT_TYPE.DROPDOWN_MENU_CLEAR, null); // 清除菜单 diff --git a/packages/vtable/src/scenegraph/component/menu.ts b/packages/vtable/src/scenegraph/component/menu.ts index 9126f93e2..a68bcb292 100644 --- a/packages/vtable/src/scenegraph/component/menu.ts +++ b/packages/vtable/src/scenegraph/component/menu.ts @@ -317,6 +317,7 @@ export class MenuHandler { const resultMenuInfo = this.getEventInfo(target as unknown as Group); const resultTableInfo = this._table.getMenuInfo(this._menuInfo.col, this._menuInfo.row, this._menuInfo.type); const result = Object.assign(resultMenuInfo, resultTableInfo); + result.event = e.nativeEvent; this._table.fireListeners(TABLE_EVENT_TYPE.DROPDOWN_MENU_CLICK, result); // 由DROPDOWNMENU_CLICK事件清空菜单 diff --git a/packages/vtable/src/ts-types/menu.ts b/packages/vtable/src/ts-types/menu.ts index 62ec392d4..9eb7834fc 100644 --- a/packages/vtable/src/ts-types/menu.ts +++ b/packages/vtable/src/ts-types/menu.ts @@ -89,4 +89,5 @@ export type DropDownMenuEventInfo = { cellHeaderPaths?: ICellHeaderPaths; cellLocation: CellLocation; + event: Event; }; From 65b0065316a32810015dd1aec49c7e15fdb9d3bf Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 26 Feb 2024 12:22:46 +0800 Subject: [PATCH 44/63] refactor: rename resize_column_end event arguments #1129 --- packages/vtable/src/event/listener/table-group.ts | 2 +- packages/vtable/src/ts-types/events.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vtable/src/event/listener/table-group.ts b/packages/vtable/src/event/listener/table-group.ts index 8f4024f3c..9081b9582 100644 --- a/packages/vtable/src/event/listener/table-group.ts +++ b/packages/vtable/src/event/listener/table-group.ts @@ -750,7 +750,7 @@ function endResizeCol(table: BaseTableAPI) { } table.fireListeners(TABLE_EVENT_TYPE.RESIZE_COLUMN_END, { col: table.stateManager.columnResize.col, - columns + colWidths: columns }); } } diff --git a/packages/vtable/src/ts-types/events.ts b/packages/vtable/src/ts-types/events.ts index 192d83207..22a3301c3 100644 --- a/packages/vtable/src/ts-types/events.ts +++ b/packages/vtable/src/ts-types/events.ts @@ -82,7 +82,7 @@ export interface TableEventHandlersEventArgumentMap { scrollRatioY?: number; }; resize_column: { col: number; colWidth: number }; - resize_column_end: { col: number; columns: number[] }; + resize_column_end: { col: number; colWidths: number[] }; change_header_position: { source: CellAddress; target: CellAddress }; sort_click: { field: FieldDef; From 2512df6fb91648e727691cdf80a5c2116b96d570 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 26 Feb 2024 13:58:21 +0800 Subject: [PATCH 45/63] docs: update changlog of rush --- .../vtable/1129-refactor-event_2024-02-26-05-58.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-05-58.json diff --git a/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-05-58.json b/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-05-58.json new file mode 100644 index 000000000..62aa7e852 --- /dev/null +++ b/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-05-58.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "refactor: rename resize_column_end event arguments #1129\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 7093b927d5e8a2e2c7c684e799b41b933d1f2f17 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 26 Feb 2024 14:19:05 +0800 Subject: [PATCH 46/63] refactor: add event argument for table events --- packages/vtable/src/event/event.ts | 12 +++++++----- packages/vtable/src/state/sort/index.ts | 5 +++-- packages/vtable/src/state/state.ts | 12 +++++++----- packages/vtable/src/ts-types/events.ts | 6 +++++- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/vtable/src/event/event.ts b/packages/vtable/src/event/event.ts index a708da566..39b56adc1 100644 --- a/packages/vtable/src/event/event.ts +++ b/packages/vtable/src/event/event.ts @@ -90,12 +90,12 @@ export class EventManager { // 图标点击 this.table.on(TABLE_EVENT_TYPE.ICON_CLICK, iconInfo => { - const { col, row, x, y, funcType, icon } = iconInfo; + const { col, row, x, y, funcType, icon, event } = iconInfo; // 下拉菜单按钮点击 if (funcType === IconFuncTypeEnum.dropDown) { - stateManager.triggerDropDownMenu(col, row, x, y); + stateManager.triggerDropDownMenu(col, row, x, y, event); } else if (funcType === IconFuncTypeEnum.sort) { - stateManager.triggerSort(col, row, icon); + stateManager.triggerSort(col, row, icon, event); } else if (funcType === IconFuncTypeEnum.frozen) { stateManager.triggerFreeze(col, row, icon); } else if (funcType === IconFuncTypeEnum.drillDown) { @@ -361,7 +361,8 @@ export class EventManager { col, row, funcType: icon.attribute.funcType, - icon + icon, + event }); return true; @@ -376,7 +377,8 @@ export class EventManager { col, row, funcType: (icon.attribute as any).funcType, - icon: icon as unknown as Icon + icon: icon as unknown as Icon, + event }); return true; } diff --git a/packages/vtable/src/state/sort/index.ts b/packages/vtable/src/state/sort/index.ts index 6fdc7a3f1..149e1f697 100644 --- a/packages/vtable/src/state/sort/index.ts +++ b/packages/vtable/src/state/sort/index.ts @@ -10,7 +10,7 @@ import type { BaseTableAPI } from '../../ts-types/base-table'; * @param {BaseTableAPI} table * @return {*} */ -export function dealSort(col: number, row: number, table: ListTableAPI) { +export function dealSort(col: number, row: number, table: ListTableAPI, event: Event) { //是击中的sort按钮才进行排序 let range1 = null; let tableState: SortState; @@ -53,8 +53,9 @@ export function dealSort(col: number, row: number, table: ListTableAPI) { order: 'normal' }; } + (tableState as SortState & { event: Event }).event = event; // 如果用户监听SORT_CLICK事件的回调函数返回false 则不执行内部排序逻辑 - const sortEventReturns = table.fireListeners(TABLE_EVENT_TYPE.SORT_CLICK, tableState); + const sortEventReturns = table.fireListeners(TABLE_EVENT_TYPE.SORT_CLICK, tableState as SortState & { event: Event }); if (sortEventReturns.includes(false)) { return; } diff --git a/packages/vtable/src/state/state.ts b/packages/vtable/src/state/state.ts index cbbe4a4df..8a8526421 100644 --- a/packages/vtable/src/state/state.ts +++ b/packages/vtable/src/state/state.ts @@ -788,10 +788,11 @@ export class StateManager { } } - triggerDropDownMenu(col: number, row: number, x: number, y: number) { + triggerDropDownMenu(col: number, row: number, x: number, y: number, event: Event) { this.table.fireListeners(TABLE_EVENT_TYPE.DROPDOWN_ICON_CLICK, { col, - row + row, + event }); if (this.menu.isShow) { this.hideMenu(); @@ -935,7 +936,7 @@ export class StateManager { } return false; } - triggerSort(col: number, row: number, iconMark: Icon) { + triggerSort(col: number, row: number, iconMark: Icon, event: Event) { if (this.table.isPivotTable()) { // 透视表不执行sort操作 const order = (this.table as PivotTableAPI).getPivotSortState(col, row); @@ -945,7 +946,8 @@ export class StateManager { row: row, order: order || 'normal', dimensionInfo: (this.table.internalProps.layoutMap as PivotHeaderLayoutMap).getPivotDimensionInfo(col, row), - cellLocation: this.table.getCellLocation(col, row) + cellLocation: this.table.getCellLocation(col, row), + event }); return; } @@ -953,7 +955,7 @@ export class StateManager { const oldSortCol = this.sort.col; const oldSortRow = this.sort.row; // 执行sort - dealSort(col, row, this.table as ListTableAPI); + dealSort(col, row, this.table as ListTableAPI, event); this.sort.col = col; this.sort.row = row; diff --git a/packages/vtable/src/ts-types/events.ts b/packages/vtable/src/ts-types/events.ts index 22a3301c3..b88454bea 100644 --- a/packages/vtable/src/ts-types/events.ts +++ b/packages/vtable/src/ts-types/events.ts @@ -87,6 +87,7 @@ export interface TableEventHandlersEventArgumentMap { sort_click: { field: FieldDef; order: SortOrder; + event: Event; }; freeze_click: { col: number; row: number; fields: FieldDef[]; colCount: number }; dropdown_menu_click: DropDownMenuEventArgs; @@ -97,7 +98,7 @@ export interface TableEventHandlersEventArgumentMap { copy_data: { cellRange: CellRange[]; copyData: string }; drillmenu_click: DrillMenuEventInfo; - dropdown_icon_click: CellAddress; + dropdown_icon_click: CellAddress & { event: Event }; dropdown_menu_clear: CellAddress; show_menu: { @@ -116,6 +117,7 @@ export interface TableEventHandlersEventArgumentMap { y: number; funcType?: IconFuncTypeEnum | string; icon: Icon; + event: Event; }; pivot_sort_click: { @@ -124,6 +126,7 @@ export interface TableEventHandlersEventArgumentMap { order: SortOrder; dimensionInfo: IDimensionInfo[]; cellLocation: CellLocation; + event: Event; }; tree_hierarchy_state_change: { col: number; @@ -164,6 +167,7 @@ export interface DrillMenuEventInfo { drillUp: boolean; col: number; row: number; + event: Event; } export interface TableEventHandlersReturnMap { selected_cell: void; From 5b857acd3e4e244b5c50f6a254d098d7c6b15c34 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 26 Feb 2024 14:50:13 +0800 Subject: [PATCH 47/63] refactor: api return value type --- packages/vtable/src/ts-types/base-table.ts | 12 ++++++------ packages/vtable/src/ts-types/common.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 174edc80a..d63457893 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -509,12 +509,12 @@ export interface BaseTableAPI { setMaxColWidth: (col: number, maxwidth: string | number) => void; getMinColWidth: (col: number) => number; setMinColWidth: (col: number, minwidth: string | number) => void; - getCellRect: (col: number, row: number) => RectProps; - getCellRelativeRect: (col: number, row: number) => RectProps; - getCellsRect: (startCol: number, startRow: number, endCol: number, endRow: number) => RectProps; - getCellRangeRect: (cellRange: CellRange | CellAddress) => RectProps; - getCellRangeRelativeRect: (cellRange: CellRange | CellAddress) => RectProps; - getVisibleCellRangeRelativeRect: (cellRange: CellRange | CellAddress) => RectProps; + getCellRect: (col: number, row: number) => Rect; + getCellRelativeRect: (col: number, row: number) => Rect; + getCellsRect: (startCol: number, startRow: number, endCol: number, endRow: number) => Rect; + getCellRangeRect: (cellRange: CellRange | CellAddress) => Rect; + getCellRangeRelativeRect: (cellRange: CellRange | CellAddress) => Rect; + getVisibleCellRangeRelativeRect: (cellRange: CellRange | CellAddress) => Rect; isFrozenCell: (col: number, row: number) => { row: boolean; col: boolean } | null; getRowAt: (absoluteY: number) => { top: number; row: number; bottom: number }; getColAt: (absoluteX: number) => { left: number; col: number; right: number }; diff --git a/packages/vtable/src/ts-types/common.ts b/packages/vtable/src/ts-types/common.ts index b9910d0bb..3647ca810 100644 --- a/packages/vtable/src/ts-types/common.ts +++ b/packages/vtable/src/ts-types/common.ts @@ -1,6 +1,6 @@ import type { ColumnTypeOption } from './column'; -import type { ColumnData } from './list-table/layout-map/api'; import type { CellLocation, FieldData, FieldDef } from './table-engine'; +import type { Rect } from '../tools/Rect'; export type MaybePromise = T | Promise; @@ -49,7 +49,7 @@ export type CellInfo = { /**单元格行列表头paths */ cellHeaderPaths?: ICellHeaderPaths; /**单元格的位置 */ - cellRange?: RectProps; + cellRange?: Rect; /**整条数据-原始数据 */ originData?: any; /**format之后的值 */ From 3508cfa97bb48fca65908a24b52f255287d2d5a9 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 26 Feb 2024 14:50:32 +0800 Subject: [PATCH 48/63] docs: update changlog of rush --- .../vtable/1129-refactor-event_2024-02-26-06-50.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-06-50.json diff --git a/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-06-50.json b/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-06-50.json new file mode 100644 index 000000000..6f8d2d043 --- /dev/null +++ b/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-06-50.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "refactor: api return value type\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 92a88370742b287d7f6ebefe9447f0679e6bccef Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 26 Feb 2024 18:47:36 +0800 Subject: [PATCH 49/63] refactor: remove Circular dependency --- .../vtable/src/scenegraph/component/custom.ts | 57 --------- .../scenegraph/group-creater/cell-helper.ts | 111 +++++++++++++++++- .../scenegraph/group-creater/column-helper.ts | 60 +--------- .../src/scenegraph/layout/update-height.ts | 4 +- .../src/scenegraph/layout/update-width.ts | 7 +- 5 files changed, 115 insertions(+), 124 deletions(-) diff --git a/packages/vtable/src/scenegraph/component/custom.ts b/packages/vtable/src/scenegraph/component/custom.ts index f27d3d4cf..c807fd7a5 100644 --- a/packages/vtable/src/scenegraph/component/custom.ts +++ b/packages/vtable/src/scenegraph/component/custom.ts @@ -19,11 +19,6 @@ import type { import { Icon } from '../graphic/icon'; import type { BaseTableAPI } from '../../ts-types/base-table'; import type { percentCalcObj } from '../../render/layout'; -import { ProgressBarStyle } from '../../body-helper/style/ProgressBarStyle'; -import { getQuadProps } from '../utils/padding'; -import { getProp } from '../utils/get-prop'; -import type { Group } from '../graphic/group'; -import { resizeCellGroup } from '../group-creater/column-helper'; export function dealWithCustom( customLayout: ICustomLayout, @@ -467,55 +462,3 @@ function parseToGraphic(g: any, props: any) { // } } } - -export function getCustomCellMergeCustom(col: number, row: number, cellGroup: Group, table: BaseTableAPI) { - if (table.internalProps.customMergeCell) { - const customMerge = table.getCustomMerge(col, row); - if (customMerge) { - const { - range: customMergeRange, - text: customMergeText, - style: customMergeStyle, - customLayout: customMergeLayout, - customRender: customMergeRender - } = customMerge; - - if (customMergeLayout || customMergeRender) { - const customResult = dealWithCustom( - customMergeLayout, - customMergeRender, - customMergeRange.start.col, - customMergeRange.start.row, - table.getColsWidth(customMergeRange.start.col, customMergeRange.end.col), - table.getRowsHeight(customMergeRange.start.row, customMergeRange.end.row), - false, - table.heightMode === 'autoHeight', - [0, 0, 0, 0], - table - ); - - const customElementsGroup = customResult.elementsGroup; - - if (cellGroup.childrenCount > 0 && customElementsGroup) { - cellGroup.insertBefore(customElementsGroup, cellGroup.firstChild); - } else if (customElementsGroup) { - cellGroup.appendChild(customElementsGroup); - } - - const rangeHeight = table.getRowHeight(row); - const rangeWidth = table.getColWidth(col); - - const { width: contentWidth } = cellGroup.attribute; - const { height: contentHeight } = cellGroup.attribute; - cellGroup.contentWidth = contentWidth; - cellGroup.contentHeight = contentHeight; - - resizeCellGroup(cellGroup, rangeWidth, rangeHeight, customMergeRange, table); - - return customResult; - } - } - } - - return undefined; -} diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index db6fa6b9c..81682890f 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -7,7 +7,6 @@ import type { CheckboxColumnDefine, ColumnDefine, ColumnTypeOption, - ICustomRender, ImageColumnDefine, MappingRule, ProgressbarColumnDefine, @@ -22,7 +21,6 @@ import { createProgressBarCell } from './cell-type/progress-bar-cell'; import { createSparkLineCellGroup } from './cell-type/spark-line-cell'; import { createCellGroup } from './cell-type/text-cell'; import { createVideoCellGroup } from './cell-type/video-cell'; -import type { ICustomLayoutFuc } from '../../ts-types/customLayout'; import type { BaseTableAPI, PivotTableProtected } from '../../ts-types/base-table'; import { getCellCornerRadius, getStyleTheme } from '../../core/tableHelper'; import { isPromise } from '../../tools/helper'; @@ -31,11 +29,11 @@ import { CartesianAxis } from '../../components/axis/axis'; import { createCheckboxCellGroup } from './cell-type/checkbox-cell'; // import type { PivotLayoutMap } from '../../layout/pivot-layout'; import type { PivotHeaderLayoutMap } from '../../layout/pivot-header-layout'; -import { resizeCellGroup } from './column-helper'; import { getHierarchyOffset } from '../utils/get-hierarchy-offset'; import { getQuadProps } from '../utils/padding'; import { convertInternal } from '../../tools/util'; import { updateCellContentHeight, updateCellContentWidth } from '../utils/text-icon-layout'; +import { isArray } from '@visactor/vutils'; export function createCell( type: ColumnTypeOption, @@ -747,3 +745,110 @@ function dealWithMergeCellSize( } } } + +export function resizeCellGroup( + cellGroup: Group, + rangeWidth: number, + rangeHeight: number, + range: CellRange, + table: BaseTableAPI +) { + const { col, row } = cellGroup; + const dx = -table.getColsWidth(range.start.col, col - 1); + const dy = -table.getRowsHeight(range.start.row, row - 1); + + cellGroup.forEachChildren((child: IGraphic) => { + child.setAttributes({ + dx: (child.attribute.dx ?? 0) + dx, + dy: (child.attribute.dy ?? 0) + dy + }); + }); + + const lineWidth = cellGroup.attribute.lineWidth; + const isLineWidthArray = isArray(lineWidth); + const newLineWidth = [0, 0, 0, 0]; + + if (col === range.start.col) { + newLineWidth[3] = isLineWidthArray ? lineWidth[3] : lineWidth; + } + if (row === range.start.row) { + newLineWidth[0] = isLineWidthArray ? lineWidth[0] : lineWidth; + } + if (col === range.end.col) { + newLineWidth[1] = isLineWidthArray ? lineWidth[1] : lineWidth; + } + if (row === range.end.row) { + newLineWidth[2] = isLineWidthArray ? lineWidth[2] : lineWidth; + } + + const widthChange = rangeWidth !== cellGroup.attribute.width; + const heightChange = rangeHeight !== cellGroup.attribute.height; + + cellGroup.setAttributes({ + width: rangeWidth, + height: rangeHeight, + strokeArrayWidth: newLineWidth + } as any); + + cellGroup.mergeStartCol = range.start.col; + cellGroup.mergeStartRow = range.start.row; + cellGroup.mergeEndCol = range.end.col; + cellGroup.mergeEndRow = range.end.row; + + return { + widthChange, + heightChange + }; +} + +export function getCustomCellMergeCustom(col: number, row: number, cellGroup: Group, table: BaseTableAPI) { + if (table.internalProps.customMergeCell) { + const customMerge = table.getCustomMerge(col, row); + if (customMerge) { + const { + range: customMergeRange, + text: customMergeText, + style: customMergeStyle, + customLayout: customMergeLayout, + customRender: customMergeRender + } = customMerge; + + if (customMergeLayout || customMergeRender) { + const customResult = dealWithCustom( + customMergeLayout, + customMergeRender, + customMergeRange.start.col, + customMergeRange.start.row, + table.getColsWidth(customMergeRange.start.col, customMergeRange.end.col), + table.getRowsHeight(customMergeRange.start.row, customMergeRange.end.row), + false, + table.heightMode === 'autoHeight', + [0, 0, 0, 0], + table + ); + + const customElementsGroup = customResult.elementsGroup; + + if (cellGroup.childrenCount > 0 && customElementsGroup) { + cellGroup.insertBefore(customElementsGroup, cellGroup.firstChild); + } else if (customElementsGroup) { + cellGroup.appendChild(customElementsGroup); + } + + const rangeHeight = table.getRowHeight(row); + const rangeWidth = table.getColWidth(col); + + const { width: contentWidth } = cellGroup.attribute; + const { height: contentHeight } = cellGroup.attribute; + cellGroup.contentWidth = contentWidth; + cellGroup.contentHeight = contentHeight; + + resizeCellGroup(cellGroup, rangeWidth, rangeHeight, customMergeRange, table); + + return customResult; + } + } + } + + return undefined; +} diff --git a/packages/vtable/src/scenegraph/group-creater/column-helper.ts b/packages/vtable/src/scenegraph/group-creater/column-helper.ts index 8ab56f01a..3621f1621 100644 --- a/packages/vtable/src/scenegraph/group-creater/column-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/column-helper.ts @@ -1,15 +1,14 @@ /* eslint-disable no-undef */ -import type { IGraphic, IThemeSpec } from '@src/vrender'; +import type { IThemeSpec } from '@src/vrender'; import type { CellLocation, CellRange, TextColumnDefine } from '../../ts-types'; import type { Group } from '../graphic/group'; import { getProp, getRawProp } from '../utils/get-prop'; import type { MergeMap } from '../scenegraph'; -import { createCell } from './cell-helper'; +import { createCell, resizeCellGroup } from './cell-helper'; import type { BaseTableAPI } from '../../ts-types/base-table'; import { getCellCornerRadius, getStyleTheme } from '../../core/tableHelper'; import { isPromise } from '../../tools/helper'; import { dealPromiseData } from '../utils/deal-promise-data'; -import { isArray } from '@visactor/vutils'; import { dealWithCustom } from '../component/custom'; /** * 创建复合列 同一列支持创建不同类型单元格 @@ -270,61 +269,6 @@ export function getColumnGroupTheme( return { theme: columnTheme, hasFunctionPros }; } -export function resizeCellGroup( - cellGroup: Group, - rangeWidth: number, - rangeHeight: number, - range: CellRange, - table: BaseTableAPI -) { - const { col, row } = cellGroup; - const dx = -table.getColsWidth(range.start.col, col - 1); - const dy = -table.getRowsHeight(range.start.row, row - 1); - - cellGroup.forEachChildren((child: IGraphic) => { - child.setAttributes({ - dx: (child.attribute.dx ?? 0) + dx, - dy: (child.attribute.dy ?? 0) + dy - }); - }); - - const lineWidth = cellGroup.attribute.lineWidth; - const isLineWidthArray = isArray(lineWidth); - const newLineWidth = [0, 0, 0, 0]; - - if (col === range.start.col) { - newLineWidth[3] = isLineWidthArray ? lineWidth[3] : lineWidth; - } - if (row === range.start.row) { - newLineWidth[0] = isLineWidthArray ? lineWidth[0] : lineWidth; - } - if (col === range.end.col) { - newLineWidth[1] = isLineWidthArray ? lineWidth[1] : lineWidth; - } - if (row === range.end.row) { - newLineWidth[2] = isLineWidthArray ? lineWidth[2] : lineWidth; - } - - const widthChange = rangeWidth !== cellGroup.attribute.width; - const heightChange = rangeHeight !== cellGroup.attribute.height; - - cellGroup.setAttributes({ - width: rangeWidth, - height: rangeHeight, - strokeArrayWidth: newLineWidth - } as any); - - cellGroup.mergeStartCol = range.start.col; - cellGroup.mergeStartRow = range.start.row; - cellGroup.mergeEndCol = range.end.col; - cellGroup.mergeEndRow = range.end.row; - - return { - widthChange, - heightChange - }; -} - function dealMerge(range: CellRange, mergeMap: MergeMap, table: BaseTableAPI, forceUpdate: boolean) { let cellWidth = 0; let cellHeight = 0; diff --git a/packages/vtable/src/scenegraph/layout/update-height.ts b/packages/vtable/src/scenegraph/layout/update-height.ts index 5326ed59a..598c7366a 100644 --- a/packages/vtable/src/scenegraph/layout/update-height.ts +++ b/packages/vtable/src/scenegraph/layout/update-height.ts @@ -8,12 +8,12 @@ import { getProp } from '../utils/get-prop'; import { getQuadProps } from '../utils/padding'; import { updateCellContentHeight } from '../utils/text-icon-layout'; import type { IProgressbarColumnBodyDefine } from '../../ts-types/list-table/define/progressbar-define'; -import { dealWithCustom, getCustomCellMergeCustom } from '../component/custom'; +import { dealWithCustom } from '../component/custom'; import { updateImageCellContentWhileResize } from '../group-creater/cell-type/image-cell'; import { getStyleTheme } from '../../core/tableHelper'; import { isMergeCellGroup } from '../utils/is-merge-cell-group'; import type { BaseTableAPI } from '../../ts-types/base-table'; -import { resizeCellGroup } from '../group-creater/column-helper'; +import { resizeCellGroup, getCustomCellMergeCustom } from '../group-creater/cell-helper'; import type { IGraphic } from '@src/vrender'; import { getCellMergeRange } from '../../tools/merge-range'; diff --git a/packages/vtable/src/scenegraph/layout/update-width.ts b/packages/vtable/src/scenegraph/layout/update-width.ts index c0c34d06b..6b9807d33 100644 --- a/packages/vtable/src/scenegraph/layout/update-width.ts +++ b/packages/vtable/src/scenegraph/layout/update-width.ts @@ -4,20 +4,19 @@ import { CartesianAxis } from '../../components/axis/axis'; import { getStyleTheme } from '../../core/tableHelper'; import type { BaseTableAPI } from '../../ts-types/base-table'; import type { IProgressbarColumnBodyDefine } from '../../ts-types/list-table/define/progressbar-define'; -import { dealWithCustom, getCustomCellMergeCustom } from '../component/custom'; +import { dealWithCustom } from '../component/custom'; import type { Group } from '../graphic/group'; -import type { Icon } from '../graphic/icon'; import { updateImageCellContentWhileResize } from '../group-creater/cell-type/image-cell'; import { createProgressBarCell } from '../group-creater/cell-type/progress-bar-cell'; import { createSparkLineCellGroup } from '../group-creater/cell-type/spark-line-cell'; -import { resizeCellGroup } from '../group-creater/column-helper'; +import { resizeCellGroup, getCustomCellMergeCustom } from '../group-creater/cell-helper'; import type { Scenegraph } from '../scenegraph'; import { getCellMergeInfo } from '../utils/get-cell-merge'; import { getProp } from '../utils/get-prop'; import { isMergeCellGroup } from '../utils/is-merge-cell-group'; import { getQuadProps } from '../utils/padding'; import { updateCellContentWidth } from '../utils/text-icon-layout'; -import { computeRowHeight, computeRowsHeight } from './compute-row-height'; +import { computeRowHeight } from './compute-row-height'; import { updateCellHeightForRow } from './update-height'; import { getHierarchyOffset } from '../utils/get-hierarchy-offset'; import { getCellMergeRange } from '../../tools/merge-range'; From 34f66683f3a936de7c742e2d0f13106d01de695b Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 26 Feb 2024 18:48:02 +0800 Subject: [PATCH 50/63] docs: update changlog of rush --- ...refactor-circular_dependency_2024-02-26-10-48.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/refactor-circular_dependency_2024-02-26-10-48.json diff --git a/common/changes/@visactor/vtable/refactor-circular_dependency_2024-02-26-10-48.json b/common/changes/@visactor/vtable/refactor-circular_dependency_2024-02-26-10-48.json new file mode 100644 index 000000000..3a87ecd36 --- /dev/null +++ b/common/changes/@visactor/vtable/refactor-circular_dependency_2024-02-26-10-48.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "refactor: remove Circular dependency\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 26fb47c6f4cec08eae82a26828e3f6fab3705af5 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Tue, 27 Feb 2024 15:36:56 +0800 Subject: [PATCH 51/63] docs: refix list table analysis guide address --- .../list-table-data-analysis/list-table-aggregation-multiple.md | 2 +- .../demo/en/list-table-data-analysis/list-table-aggregation.md | 2 +- .../demo/en/list-table-data-analysis/list-table-data-filter.md | 2 +- .../list-table-data-analysis/list-table-aggregation-multiple.md | 2 +- .../demo/zh/list-table-data-analysis/list-table-aggregation.md | 2 +- .../demo/zh/list-table-data-analysis/list-table-data-filter.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation-multiple.md b/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation-multiple.md index 636017f85..3f9de5b0d 100644 --- a/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation-multiple.md +++ b/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation-multiple.md @@ -3,7 +3,7 @@ category: examples group: list-table-data-analysis title: Set multiple aggregation and aggregation summary methods for the same column of data cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-multiple-aggregation.png -link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +link: '../guide/data_analysis/list_table_dataAnalysis' option: ListTable-columns-text#aggregation(Aggregation%20%7C%20CustomAggregation%20%7C%20Array) --- diff --git a/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation.md b/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation.md index b7d6377f0..95d39946d 100644 --- a/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation.md +++ b/docs/assets/demo/en/list-table-data-analysis/list-table-aggregation.md @@ -3,7 +3,7 @@ category: examples group: list-table-data-analysis title: List Table data aggregation summary cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-aggregation.png -link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +link: '../guide/data_analysis/list_table_dataAnalysis' option: ListTable-columns-text#aggregation(Aggregation%20%7C%20CustomAggregation%20%7C%20Array) --- diff --git a/docs/assets/demo/en/list-table-data-analysis/list-table-data-filter.md b/docs/assets/demo/en/list-table-data-analysis/list-table-data-filter.md index 872b0df2e..d66c6424e 100644 --- a/docs/assets/demo/en/list-table-data-analysis/list-table-data-filter.md +++ b/docs/assets/demo/en/list-table-data-analysis/list-table-data-filter.md @@ -3,7 +3,7 @@ category: examples group: list-table-data-analysis title: List table data filtering cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-filter.gif -link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +link: '../guide/data_analysis/list_table_dataAnalysis' --- # List table data filtering diff --git a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md index 2cfd7ce6a..b38121d38 100644 --- a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md +++ b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation-multiple.md @@ -3,7 +3,7 @@ category: examples group: list-table-data-analysis title: 同一列数据设置多种聚合汇总方式 cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-multiple-aggregation.png -link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +link: '../guide/data_analysis/list_table_dataAnalysis' option: ListTable-columns-text#aggregation(Aggregation%20%7C%20CustomAggregation%20%7C%20Array) --- diff --git a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md index b60febfa7..b83808d9a 100644 --- a/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md +++ b/docs/assets/demo/zh/list-table-data-analysis/list-table-aggregation.md @@ -3,7 +3,7 @@ category: examples group: list-table-data-analysis title: 基本表格数据聚合分析 cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-aggregation.png -link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +link: '../guide/data_analysis/list_table_dataAnalysis' option: ListTable-columns-text#aggregation(Aggregation%20%7C%20CustomAggregation%20%7C%20Array) --- diff --git a/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md b/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md index 5caba692b..97f48f9f7 100644 --- a/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md +++ b/docs/assets/demo/zh/list-table-data-analysis/list-table-data-filter.md @@ -3,7 +3,7 @@ category: examples group: list-table-data-analysis title: 基本表格数据过滤 cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/list-table-filter.gif -link: '../guide/table_type/Pivot_table/list_table_dataAnalysis' +link: '../guide/data_analysis/list_table_dataAnalysis' --- # 基本表格数据过滤 From 96b1d42f29754ce19907fcfb964bdc77dddbf27b Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Tue, 27 Feb 2024 10:55:16 +0800 Subject: [PATCH 52/63] fix: reset theme rowHeaderStyle config --- packages/vtable/src/core/BaseTable.ts | 32 ++++++++++---------- packages/vtable/src/scenegraph/scenegraph.ts | 6 ++-- packages/vtable/src/themes/theme.ts | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 247d0b536..a69624984 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -2981,16 +2981,16 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.options.autoWrapText ); } else { - let defaultStyle; - if (layoutMap.isColumnHeader(col, row) || layoutMap.isBottomFrozenRow(col, row)) { - defaultStyle = this.theme.headerStyle; - } else if (this.internalProps.transpose && layoutMap.isRowHeader(col, row)) { - defaultStyle = this.theme.headerStyle; - } else if (layoutMap.isRowHeader(col, row) || layoutMap.isRightFrozenColumn(col, row)) { - defaultStyle = this.theme.rowHeaderStyle; - } else { - defaultStyle = this.theme.cornerHeaderStyle; - } + // let defaultStyle; + // if (layoutMap.isColumnHeader(col, row) || layoutMap.isBottomFrozenRow(col, row)) { + // defaultStyle = this.theme.headerStyle; + // } else if (this.internalProps.transpose && layoutMap.isRowHeader(col, row)) { + // defaultStyle = this.theme.headerStyle; + // } else if (layoutMap.isRowHeader(col, row) || layoutMap.isRightFrozenColumn(col, row)) { + // defaultStyle = this.theme.rowHeaderStyle; + // } else { + // defaultStyle = this.theme.cornerHeaderStyle; + // } // const styleClass = hd.headerType.StyleClass; //BaseHeader文件 // const { style } = hd; const style = hd?.style || {}; @@ -2999,12 +2999,12 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { } cacheStyle = headerStyleContents.of( style, - defaultStyle, - // layoutMap.isColumnHeader(col, row) || layoutMap.isBottomFrozenRow(col, row) - // ? this.theme.headerStyle - // : layoutMap.isRowHeader(col, row) || layoutMap.isRightFrozenColumn(col, row) - // ? this.theme.rowHeaderStyle - // : this.theme.cornerHeaderStyle, + // defaultStyle, + layoutMap.isColumnHeader(col, row) || layoutMap.isBottomFrozenRow(col, row) + ? this.theme.headerStyle + : layoutMap.isRowHeader(col, row) || layoutMap.isRightFrozenColumn(col, row) + ? this.theme.rowHeaderStyle + : this.theme.cornerHeaderStyle, { col, row, diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index a5fa857f6..a5dd686ef 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -1494,9 +1494,9 @@ export class Scenegraph { this.rowHeaderGroup, this.isPivot ? this.table.theme.rowHeaderStyle.frameStyle - : this.table.internalProps.transpose - ? this.table.theme.headerStyle.frameStyle - : this.table.theme.bodyStyle.frameStyle, + : // : this.table.internalProps.transpose + // ? this.table.theme.headerStyle.frameStyle + this.table.theme.bodyStyle.frameStyle, this.rowHeaderGroup.role, isListTableWithFrozen ? [true, false, true, true] : undefined ); diff --git a/packages/vtable/src/themes/theme.ts b/packages/vtable/src/themes/theme.ts index bec33b2af..fce2ee88c 100644 --- a/packages/vtable/src/themes/theme.ts +++ b/packages/vtable/src/themes/theme.ts @@ -376,7 +376,7 @@ export class TableTheme implements ITableThemeDefine { {}, this.defaultStyle, superTheme.rowHeaderStyle, - obj.rowHeaderStyle // ?? obj.headerStyle + obj.rowHeaderStyle ?? obj.headerStyle ); this._rowHeader = this.getStyle(header); } From a00d4c5dfcec0848ded41a9ddfef437697c73db8 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 26 Feb 2024 16:14:32 +0800 Subject: [PATCH 53/63] fix: fix axis innerOffset --- ...fix-axis-innerOffset_2024-02-26-08-10.json | 10 ++++++++ packages/vtable/src/components/axis/axis.ts | 23 ++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 common/changes/@visactor/vtable/fix-axis-innerOffset_2024-02-26-08-10.json diff --git a/common/changes/@visactor/vtable/fix-axis-innerOffset_2024-02-26-08-10.json b/common/changes/@visactor/vtable/fix-axis-innerOffset_2024-02-26-08-10.json new file mode 100644 index 000000000..ae33139ee --- /dev/null +++ b/common/changes/@visactor/vtable/fix-axis-innerOffset_2024-02-26-08-10.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix: fix axis innerOffset", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/packages/vtable/src/components/axis/axis.ts b/packages/vtable/src/components/axis/axis.ts index 064e7098e..ddca94e1e 100644 --- a/packages/vtable/src/components/axis/axis.ts +++ b/packages/vtable/src/components/axis/axis.ts @@ -57,14 +57,18 @@ export class CartesianAxis { ); if (this.orient === 'left' || this.orient === 'right') { - const innerOffsetTop = this.option.innerOffset?.top ?? 0; - const innerOffsetBottom = this.option.innerOffset?.bottom ?? 0; + // const innerOffsetTop = this.option.innerOffset?.top ?? 0; + // const innerOffsetBottom = this.option.innerOffset?.bottom ?? 0; + const innerOffsetTop = 0; + const innerOffsetBottom = 0; this.width = width; this.height = height - padding[2] - innerOffsetBottom; this.y = padding[0] + innerOffsetTop; } else if (this.orient === 'top' || this.orient === 'bottom') { - const innerOffsetLeft = this.option.innerOffset?.left ?? 0; - const innerOffsetRight = this.option.innerOffset?.right ?? 0; + // const innerOffsetLeft = this.option.innerOffset?.left ?? 0; + // const innerOffsetRight = this.option.innerOffset?.right ?? 0; + const innerOffsetLeft = 0; + const innerOffsetRight = 0; this.width = width - padding[1] - innerOffsetRight; this.height = height; this.x = padding[3] + innerOffsetLeft; @@ -247,16 +251,23 @@ export class CartesianAxis { } updateScaleRange() { + const right = this.option.innerOffset?.right ?? 0; + const left = this.option.innerOffset?.left ?? 0; + const top = this.option.innerOffset?.top ?? 0; + const bottom = this.option.innerOffset?.bottom ?? 0; + const { width, height } = this.getLayoutRect(); const inverse = (this.option as any).inverse || false; let newRange: [number, number] = [0, 0]; if (isXAxis(this.orient)) { if (isValidNumber(width)) { - newRange = inverse ? [width, 0] : [0, width]; + // newRange = inverse ? [width, 0] : [0, width]; + newRange = inverse ? [width - right, left] : [left, width - right]; } } else { if (isValidNumber(height)) { - newRange = inverse ? [0, height] : [height, 0]; + // newRange = inverse ? [0, height] : [height, 0]; + newRange = inverse ? [top, height - bottom] : [height - bottom, top]; } } From 104e71956dfdb1bb5c7210494bee9576fae309fa Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 26 Feb 2024 16:39:11 +0800 Subject: [PATCH 54/63] fix: fix hierarchyExpandLevel missing in initTreeHierarchyState() --- packages/vtable/src/data/DataSource.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index d14a24592..81c324c20 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -222,18 +222,20 @@ export class DataSource extends EventTarget implements DataSourceAPI { } this.currentIndexedData = Array.from({ length: this._sourceLength }, (_, i) => i); - let nodeLength = this._sourceLength; - for (let i = 0; i < nodeLength; i++) { - const indexKey = this.currentIndexedData[i]; - const nodeData = this.getOriginalRecord(indexKey); - if ((nodeData as any).children?.length > 0) { - this.treeDataHierarchyState.set( - Array.isArray(indexKey) ? indexKey.join(',') : indexKey, - HierarchyState.expand - ); - const childrenLength = this.initChildrenNodeHierarchy(indexKey, this.hierarchyExpandLevel, 2, nodeData); - i += childrenLength; - nodeLength += childrenLength; + if (this.hierarchyExpandLevel > 1) { + let nodeLength = this._sourceLength; + for (let i = 0; i < nodeLength; i++) { + const indexKey = this.currentIndexedData[i]; + const nodeData = this.getOriginalRecord(indexKey); + if ((nodeData as any).children?.length > 0) { + this.treeDataHierarchyState.set( + Array.isArray(indexKey) ? indexKey.join(',') : indexKey, + HierarchyState.expand + ); + const childrenLength = this.initChildrenNodeHierarchy(indexKey, this.hierarchyExpandLevel, 2, nodeData); + i += childrenLength; + nodeLength += childrenLength; + } } } } From 640fa206dedfd7925a67151a2f0d2a32783d3483 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 28 Feb 2024 18:47:51 +0800 Subject: [PATCH 55/63] refactor: setRecords support restoreHierarchyState #1148 --- packages/vtable/src/ListTable.ts | 38 +++++++++++++++++++++++--- packages/vtable/src/PivotChart.ts | 2 +- packages/vtable/src/PivotTable.ts | 2 +- packages/vtable/src/core/BaseTable.ts | 5 +++- packages/vtable/src/data/DataSource.ts | 6 ++-- 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 461959eb4..602d60de0 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -89,7 +89,7 @@ export class ListTable extends BaseTable implements ListTableAPI { if (options.dataSource) { _setDataSource(this, options.dataSource); } else if (options.records) { - this.setRecords(options.records as any, internalProps.sortState); + this.setRecords(options.records as any, { sortState: internalProps.sortState }); } else { this.setRecords([]); } @@ -351,7 +351,7 @@ export class ListTable extends BaseTable implements ListTableAPI { } return ifCan; } - updateOption(options: ListTableConstructorOptions, accelerateFirstScreen = false) { + updateOption(options: ListTableConstructorOptions & { restoreHierarchyState?: boolean }) { const internalProps = this.internalProps; super.updateOption(options); internalProps.frozenColDragHeaderMode = options.frozenColDragHeaderMode; @@ -388,7 +388,10 @@ export class ListTable extends BaseTable implements ListTableAPI { if (options.dataSource) { _setDataSource(this, options.dataSource); } else if (options.records) { - this.setRecords(options.records as any, options.sortState); + this.setRecords(options.records as any, { + restoreHierarchyState: options.restoreHierarchyState, + sortState: options.sortState + }); } else { this._resetFrozenColCount(); // 生成单元格场景树 @@ -860,7 +863,17 @@ export class ListTable extends BaseTable implements ListTableAPI { * @param records * @param sort */ - setRecords(records: Array, sort?: SortState | SortState[]): void { + setRecords( + records: Array, + option?: { restoreHierarchyState?: boolean; sortState?: SortState | SortState[] } + ): void { + let sort: SortState | SortState[]; + if (Array.isArray(option) || (option as any).order) { + //兼容之前第二个参数为sort的情况 + sort = option; + } else { + sort = option.sortState; + } const time = typeof window !== 'undefined' ? window.performance.now() : 0; const oldHoverState = { col: this.stateManager.hover.cellPos.col, row: this.stateManager.hover.cellPos.row }; // 清空单元格内容 @@ -871,6 +884,10 @@ export class ListTable extends BaseTable implements ListTableAPI { this.internalProps.sortState = sort; this.stateManager.setSortState((this as any).sortState as SortState); } + // restoreHierarchyState逻辑,保留树形结构展开收起的状态 + const currentPagerIndexedData = this.dataSource?._currentPagerIndexedData; + const currentIndexedData = this.dataSource?.currentIndexedData; + const treeDataHierarchyState = this.dataSource?.treeDataHierarchyState; if (records) { _setRecords(this, records); if ((this as any).sortState) { @@ -894,10 +911,23 @@ export class ListTable extends BaseTable implements ListTableAPI { } } } + if (option?.restoreHierarchyState) { + // restoreHierarchyState逻辑,保留树形结构展开收起的状态 + this.dataSource._currentPagerIndexedData = currentPagerIndexedData; + this.dataSource.currentIndexedData = currentIndexedData; + this.dataSource.treeDataHierarchyState = treeDataHierarchyState; + } this.refreshRowColCount(); } else { _setRecords(this, records); + if (option?.restoreHierarchyState) { + // restoreHierarchyState逻辑,保留树形结构展开收起的状态 + this.dataSource._currentPagerIndexedData = currentPagerIndexedData; + this.dataSource.currentIndexedData = currentIndexedData; + this.dataSource.treeDataHierarchyState = treeDataHierarchyState; + } } + this.stateManager.initCheckedState(records); // this.internalProps.frozenColCount = this.options.frozenColCount || this.rowHeaderLevelCount; // 生成单元格场景树 diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index d7693c12b..0d6781154 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -176,7 +176,7 @@ export class PivotChart extends BaseTable implements PivotChartAPI { } return ifCan; } - updateOption(options: PivotChartConstructorOptions, accelerateFirstScreen = false) { + updateOption(options: PivotChartConstructorOptions) { const internalProps = this.internalProps; //维护选中状态 // const range = internalProps.selection.range; //保留原有单元格选中状态 diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 74f4f912f..69114ce9f 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -206,7 +206,7 @@ export class PivotTable extends BaseTable implements PivotTableAPI { } return ifCan; } - updateOption(options: PivotTableConstructorOptions, accelerateFirstScreen = false) { + updateOption(options: PivotTableConstructorOptions) { const internalProps = this.internalProps; //维护选中状态 // const range = internalProps.selection.range; //保留原有单元格选中状态 diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index a69624984..0451b1d69 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -2427,7 +2427,10 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { field: FieldDef, fieldKey?: FieldKeyDef ): ((v1: any, v2: any, order: string) => 0 | 1 | -1) | undefined; - abstract setRecords(records: Array, sort?: SortState | SortState[]): void; + abstract setRecords( + records: Array, + option?: { restoreHierarchyState: boolean; sort?: SortState | SortState[] } + ): void; abstract refreshHeader(): void; abstract refreshRowColCount(): void; abstract getHierarchyState(col: number, row: number): HierarchyState | null; diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index 81c324c20..45e2c8ab8 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -158,17 +158,17 @@ export class DataSource extends EventTarget implements DataSourceAPI { private lastOrderFn: (a: any, b: any, order: string) => number; private lastOrderField: FieldDef; /** 每一行对应源数据的索引 */ - protected currentIndexedData: (number | number[])[] | null = []; + currentIndexedData: (number | number[])[] | null = []; protected userPagination: IPagination; protected pagination: IPagination; /** 当前页每一行对应源数据的索引 */ - protected _currentPagerIndexedData: (number | number[])[]; + _currentPagerIndexedData: (number | number[])[]; // 当前是否为层级的树形结构 排序时判断该值确实是否继续进行子节点排序 hierarchyExpandLevel: number = 0; static get EVENT_TYPE(): typeof EVENT_TYPE { return EVENT_TYPE; } - protected treeDataHierarchyState: Map = new Map(); + treeDataHierarchyState: Map = new Map(); beforeChangedRecordsMap: Record[] = []; // 注册聚合类型 From edaf5e1ff990b6c7199213865429a18bb781992d Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 28 Feb 2024 18:48:29 +0800 Subject: [PATCH 56/63] docs: update changlog of rush --- ...isttable-tree-hierarchyState_2024-02-28-10-48.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1148-feature-listtable-tree-hierarchyState_2024-02-28-10-48.json diff --git a/common/changes/@visactor/vtable/1148-feature-listtable-tree-hierarchyState_2024-02-28-10-48.json b/common/changes/@visactor/vtable/1148-feature-listtable-tree-hierarchyState_2024-02-28-10-48.json new file mode 100644 index 000000000..535a1e2de --- /dev/null +++ b/common/changes/@visactor/vtable/1148-feature-listtable-tree-hierarchyState_2024-02-28-10-48.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "refactor: setRecords support restoreHierarchyState #1148\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 70ebf94153b5329f2dadabb8c784c0237efbd80b Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 29 Feb 2024 10:59:13 +0800 Subject: [PATCH 57/63] refactor: react-table add restoreHierarchyState --- packages/react-vtable/src/tables/base-table.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-vtable/src/tables/base-table.tsx b/packages/react-vtable/src/tables/base-table.tsx index a626a4bd5..e0af7b19a 100644 --- a/packages/react-vtable/src/tables/base-table.tsx +++ b/packages/react-vtable/src/tables/base-table.tsx @@ -191,7 +191,9 @@ const BaseTable: React.FC = React.forwardRef((props, ref) => { !isEqual(eventsBinded.current.records, props.records, { skipFunction: skipFunctionDiff }) ) { eventsBinded.current = props; - tableContext.current.table.setRecords(props.records); + tableContext.current.table.setRecords(props.records, { + restoreHierarchyState: props.option.restoreHierarchyState + }); handleTableRender(); } return; @@ -212,7 +214,9 @@ const BaseTable: React.FC = React.forwardRef((props, ref) => { handleTableRender(); } else if (hasRecords && !isEqual(props.records, prevRecords.current, { skipFunction: skipFunctionDiff })) { prevRecords.current = props.records; - tableContext.current.table.setRecords(props.records); + tableContext.current.table.setRecords(props.records, { + restoreHierarchyState: props.option.restoreHierarchyState + }); handleTableRender(); } // tableContext.current = { From 0b1320fd27ffc2193b0460ba1c3485a433355f1a Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 29 Feb 2024 12:25:40 +0800 Subject: [PATCH 58/63] fix: customlayout flex render error #1163 --- packages/vtable/src/scenegraph/layout/compute-col-width.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts index ef06f55e8..7117c9821 100644 --- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts +++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts @@ -624,14 +624,14 @@ function computeTextWidth(col: number, row: number, cellType: ColumnTypeOption, return maxWidth; } -function getCellRect(col: number, row: number, table: BaseTableAPI) { +function getCellRect(col: number, row: number, table: BaseTableAPI): any { return { left: 0, top: 0, right: table.getColWidth(col), bottom: table.getRowHeight(row), - width: 0, - height: 0 + width: null, // vrender 逻辑中通过判断null对flex的元素来自动计算宽高 + height: null }; } From a4a07f8ef15c46f04ec3449c518ff994d67b08c4 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 29 Feb 2024 12:26:01 +0800 Subject: [PATCH 59/63] docs: update changlog of rush --- .../1163-bug-customlayout-flex_2024-02-29-04-26.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/1163-bug-customlayout-flex_2024-02-29-04-26.json diff --git a/common/changes/@visactor/vtable/1163-bug-customlayout-flex_2024-02-29-04-26.json b/common/changes/@visactor/vtable/1163-bug-customlayout-flex_2024-02-29-04-26.json new file mode 100644 index 000000000..38bd886cb --- /dev/null +++ b/common/changes/@visactor/vtable/1163-bug-customlayout-flex_2024-02-29-04-26.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: customlayout flex render error #1163\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From c5259f39d35d83484f85db29c9fbf3b5c424edd9 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 29 Feb 2024 14:36:16 +0800 Subject: [PATCH 60/63] refactor: restoreHierarchyState logic --- packages/vtable/src/ListTable.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 602d60de0..e774b002e 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -119,7 +119,7 @@ export class ListTable extends BaseTable implements ListTableAPI { } get records() { - return this.dataSource.source; + return this.dataSource?.source; } get recordsCount() { @@ -868,11 +868,11 @@ export class ListTable extends BaseTable implements ListTableAPI { option?: { restoreHierarchyState?: boolean; sortState?: SortState | SortState[] } ): void { let sort: SortState | SortState[]; - if (Array.isArray(option) || (option as any).order) { + if (Array.isArray(option) || (option as any)?.order) { //兼容之前第二个参数为sort的情况 sort = option; } else { - sort = option.sortState; + sort = option?.sortState; } const time = typeof window !== 'undefined' ? window.performance.now() : 0; const oldHoverState = { col: this.stateManager.hover.cellPos.col, row: this.stateManager.hover.cellPos.row }; @@ -888,6 +888,7 @@ export class ListTable extends BaseTable implements ListTableAPI { const currentPagerIndexedData = this.dataSource?._currentPagerIndexedData; const currentIndexedData = this.dataSource?.currentIndexedData; const treeDataHierarchyState = this.dataSource?.treeDataHierarchyState; + const oldRecordLength = this.records?.length ?? 0; if (records) { _setRecords(this, records); if ((this as any).sortState) { @@ -911,7 +912,7 @@ export class ListTable extends BaseTable implements ListTableAPI { } } } - if (option?.restoreHierarchyState) { + if (option?.restoreHierarchyState && oldRecordLength === this.records?.length) { // restoreHierarchyState逻辑,保留树形结构展开收起的状态 this.dataSource._currentPagerIndexedData = currentPagerIndexedData; this.dataSource.currentIndexedData = currentIndexedData; @@ -920,7 +921,7 @@ export class ListTable extends BaseTable implements ListTableAPI { this.refreshRowColCount(); } else { _setRecords(this, records); - if (option?.restoreHierarchyState) { + if (option?.restoreHierarchyState && oldRecordLength === this.records?.length) { // restoreHierarchyState逻辑,保留树形结构展开收起的状态 this.dataSource._currentPagerIndexedData = currentPagerIndexedData; this.dataSource.currentIndexedData = currentIndexedData; From f916111d224e58a8280775c45b50cda1ef73f503 Mon Sep 17 00:00:00 2001 From: l1xnan Date: Thu, 29 Feb 2024 16:08:03 +0800 Subject: [PATCH 61/63] feat: contextMenuItems add col parameter (#1166) --- ...ontextMenuItems-add-col-param-2024-02-29-08-05.json | 10 ++++++++++ .../react-vtable/src/components/component/menu.tsx | 2 +- packages/vtable/examples/style/border.ts | 4 ++-- packages/vtable/src/components/menu/dom/MenuHandler.ts | 2 +- packages/vtable/src/scenegraph/component/menu.ts | 2 +- packages/vtable/src/ts-types/base-table.ts | 4 ++-- 6 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 common/changes/@visactor/vtable/fix-contextMenuItems-add-col-param-2024-02-29-08-05.json diff --git a/common/changes/@visactor/vtable/fix-contextMenuItems-add-col-param-2024-02-29-08-05.json b/common/changes/@visactor/vtable/fix-contextMenuItems-add-col-param-2024-02-29-08-05.json new file mode 100644 index 000000000..c0023cd06 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-contextMenuItems-add-col-param-2024-02-29-08-05.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix-contextMenuItems-add-col-param", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/packages/react-vtable/src/components/component/menu.tsx b/packages/react-vtable/src/components/component/menu.tsx index 7065db12a..282e09123 100644 --- a/packages/react-vtable/src/components/component/menu.tsx +++ b/packages/react-vtable/src/components/component/menu.tsx @@ -8,7 +8,7 @@ export type MenuProps = { /** 内置下拉菜单的全局设置项 目前只针对基本表格有效 会对每个表头单元格开启默认的下拉菜单功能。代替原来的option.dropDownMenu*/ defaultHeaderMenuItems?: TYPES.MenuListItem[]; /** 右键菜单。代替原来的option.contextmenu */ - contextMenuItems?: TYPES.MenuListItem[] | ((field: string, row: number) => TYPES.MenuListItem[]); + contextMenuItems?: TYPES.MenuListItem[] | ((field: string, row: number, col: number) => TYPES.MenuListItem[]); /** 设置选中状态的菜单。代替原来的option.dropDownMenuHighlight */ dropDownMenuHighlight?: TYPES.DropDownMenuHighlightInfo[]; } & BaseComponentProps; diff --git a/packages/vtable/examples/style/border.ts b/packages/vtable/examples/style/border.ts index f1db2a667..2177184d0 100644 --- a/packages/vtable/examples/style/border.ts +++ b/packages/vtable/examples/style/border.ts @@ -311,8 +311,8 @@ export function createTable() { menu: { renderMode: 'html', defaultHeaderMenuItems: ['升序排序', '降序排序', '冻结列'], - contextMenuItems: (field: string, row: number) => { - console.log(field, row); + contextMenuItems: (field: string, row: number, col: number) => { + console.log(field, row, col); return [ { text: '复制表头', menuKey: '复制表头$1' }, { text: '复制单元格', menuKey: '复制单元格$1' } diff --git a/packages/vtable/src/components/menu/dom/MenuHandler.ts b/packages/vtable/src/components/menu/dom/MenuHandler.ts index bcf50a26f..f720a7488 100644 --- a/packages/vtable/src/components/menu/dom/MenuHandler.ts +++ b/packages/vtable/src/components/menu/dom/MenuHandler.ts @@ -203,7 +203,7 @@ export class MenuHandler { const abstractPos = table._getMouseAbstractPoint(e.event, false); let menu = null; if (abstractPos.inTable && typeof table.internalProps.menu?.contextMenuItems === 'function') { - menu = table.internalProps.menu.contextMenuItems(table.getHeaderField(e.col, e.row) as string, e.row); + menu = table.internalProps.menu.contextMenuItems(table.getHeaderField(e.col, e.row) as string, e.row, e.col); } else if (abstractPos.inTable && Array.isArray(table.internalProps.menu?.contextMenuItems)) { menu = table.internalProps.menu?.contextMenuItems; } diff --git a/packages/vtable/src/scenegraph/component/menu.ts b/packages/vtable/src/scenegraph/component/menu.ts index a68bcb292..5b6526332 100644 --- a/packages/vtable/src/scenegraph/component/menu.ts +++ b/packages/vtable/src/scenegraph/component/menu.ts @@ -298,7 +298,7 @@ export class MenuHandler { const { field } = this._table.isHeader(col, row) ? this._table.getHeaderDefine(col, row) : this._table.getBodyColumnDefine(col, row); - menuInfo = contextmenu(field, row); + menuInfo = contextmenu(field, row, col); } return { menuInfo, diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index d63457893..f0db2dadc 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -158,7 +158,7 @@ export interface IBaseTableProtected { /** 内置下拉菜单的全局设置项 目前只针对基本表格有效 会对每个表头单元格开启默认的下拉菜单功能。代替原来的option.dropDownMenu*/ defaultHeaderMenuItems?: MenuListItem[]; /** 右键菜单。代替原来的option.contextmenu */ - contextMenuItems?: MenuListItem[] | ((field: FieldDef, row: number) => MenuListItem[]); + contextMenuItems?: MenuListItem[] | ((field: FieldDef, row: number, col: number) => MenuListItem[]); /** 设置选中状态的菜单。代替原来的option.dropDownMenuHighlight */ dropDownMenuHighlight?: DropDownMenuHighlightInfo[]; }; @@ -301,7 +301,7 @@ export interface BaseTableConstructorOptions { /** 内置下拉菜单的全局设置项 目前只针对基本表格有效 会对每个表头单元格开启默认的下拉菜单功能。代替原来的option.dropDownMenu*/ defaultHeaderMenuItems?: MenuListItem[]; /** 右键菜单。代替原来的option.contextmenu */ - contextMenuItems?: MenuListItem[] | ((field: string, row: number) => MenuListItem[]); + contextMenuItems?: MenuListItem[] | ((field: string, row: number, col: number) => MenuListItem[]); /** 设置选中状态的菜单。代替原来的option.dropDownMenuHighlight */ dropDownMenuHighlight?: DropDownMenuHighlightInfo[]; }; From d39831e6c25b1ee9aca163b8f50beab4d09af95f Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 29 Feb 2024 18:26:30 +0800 Subject: [PATCH 62/63] fix: add skipFunctionDiff in react-vtable --- .../vtable/fix-react-skip-func_2024-02-29-10-26.json | 10 ++++++++++ packages/react-vtable/src/tables/base-table.tsx | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 common/changes/@visactor/vtable/fix-react-skip-func_2024-02-29-10-26.json diff --git a/common/changes/@visactor/vtable/fix-react-skip-func_2024-02-29-10-26.json b/common/changes/@visactor/vtable/fix-react-skip-func_2024-02-29-10-26.json new file mode 100644 index 000000000..4fb9ad5ec --- /dev/null +++ b/common/changes/@visactor/vtable/fix-react-skip-func_2024-02-29-10-26.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix: add skipFunctionDiff in react-vtable", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/packages/react-vtable/src/tables/base-table.tsx b/packages/react-vtable/src/tables/base-table.tsx index e0af7b19a..a8027810e 100644 --- a/packages/react-vtable/src/tables/base-table.tsx +++ b/packages/react-vtable/src/tables/base-table.tsx @@ -204,7 +204,7 @@ const BaseTable: React.FC = React.forwardRef((props, ref) => { if ( !isEqual(newOption, prevOption.current, { skipFunction: skipFunctionDiff }) || // tableContext.current.isChildrenUpdated - !isEqual(newOptionFromChildren, optionFromChildren.current) + !isEqual(newOptionFromChildren, optionFromChildren.current, { skipFunction: skipFunctionDiff }) ) { prevOption.current = newOption; optionFromChildren.current = newOptionFromChildren; From 5a96241ac43cd0e7cef7c12165c2542d33f47286 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 29 Feb 2024 12:06:20 +0000 Subject: [PATCH 63/63] build: prelease version 0.20.1 --- ...lumnssubheader-error_2024-02-25-13-42.json | 11 ----- ...ds-api-for-tree-mode_2024-02-21-12-24.json | 11 ----- .../1129-refactor-event_2024-02-26-05-58.json | 11 ----- .../1129-refactor-event_2024-02-26-06-50.json | 11 ----- ...-tree-hierarchyState_2024-02-28-10-48.json | 11 ----- ...ug-customlayout-flex_2024-02-29-04-26.json | 11 ----- ...-not-stopPropagation_2024-01-10-09-31.json | 11 ----- ...905-bug-tooltip-hide_2024-02-23-03-49.json | 11 ----- ...fix-axis-innerOffset_2024-02-26-08-10.json | 10 ----- ...uItems-add-col-param-2024-02-29-08-05.json | 10 ----- .../fix-react-skip-func_2024-02-29-10-26.json | 10 ----- ...-circular_dependency_2024-02-26-10-48.json | 11 ----- common/config/rush/version-policies.json | 2 +- packages/react-vtable/package.json | 2 +- packages/vtable-editors/package.json | 2 +- packages/vtable-export/package.json | 2 +- packages/vtable/CHANGELOG.json | 45 +++++++++++++++++++ packages/vtable/CHANGELOG.md | 36 ++++++++++++++- packages/vtable/package.json | 2 +- 19 files changed, 85 insertions(+), 135 deletions(-) delete mode 100644 common/changes/@visactor/vtable/1105-bug-hidecolumnssubheader-error_2024-02-25-13-42.json delete mode 100644 common/changes/@visactor/vtable/1121-feature-list-tables-records-api-for-tree-mode_2024-02-21-12-24.json delete mode 100644 common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-05-58.json delete mode 100644 common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-06-50.json delete mode 100644 common/changes/@visactor/vtable/1148-feature-listtable-tree-hierarchyState_2024-02-28-10-48.json delete mode 100644 common/changes/@visactor/vtable/1163-bug-customlayout-flex_2024-02-29-04-26.json delete mode 100644 common/changes/@visactor/vtable/892-refactor-mousedown-not-stopPropagation_2024-01-10-09-31.json delete mode 100644 common/changes/@visactor/vtable/905-bug-tooltip-hide_2024-02-23-03-49.json delete mode 100644 common/changes/@visactor/vtable/fix-axis-innerOffset_2024-02-26-08-10.json delete mode 100644 common/changes/@visactor/vtable/fix-contextMenuItems-add-col-param-2024-02-29-08-05.json delete mode 100644 common/changes/@visactor/vtable/fix-react-skip-func_2024-02-29-10-26.json delete mode 100644 common/changes/@visactor/vtable/refactor-circular_dependency_2024-02-26-10-48.json diff --git a/common/changes/@visactor/vtable/1105-bug-hidecolumnssubheader-error_2024-02-25-13-42.json b/common/changes/@visactor/vtable/1105-bug-hidecolumnssubheader-error_2024-02-25-13-42.json deleted file mode 100644 index 799799e88..000000000 --- a/common/changes/@visactor/vtable/1105-bug-hidecolumnssubheader-error_2024-02-25-13-42.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "fix: hideColumnsSubheader with three levels show error #1105\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1121-feature-list-tables-records-api-for-tree-mode_2024-02-21-12-24.json b/common/changes/@visactor/vtable/1121-feature-list-tables-records-api-for-tree-mode_2024-02-21-12-24.json deleted file mode 100644 index ff79fbab5..000000000 --- a/common/changes/@visactor/vtable/1121-feature-list-tables-records-api-for-tree-mode_2024-02-21-12-24.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "feat: add api getRecordIndexByCell #1121\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-05-58.json b/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-05-58.json deleted file mode 100644 index 62aa7e852..000000000 --- a/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-05-58.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "refactor: rename resize_column_end event arguments #1129\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-06-50.json b/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-06-50.json deleted file mode 100644 index 6f8d2d043..000000000 --- a/common/changes/@visactor/vtable/1129-refactor-event_2024-02-26-06-50.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "refactor: api return value type\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1148-feature-listtable-tree-hierarchyState_2024-02-28-10-48.json b/common/changes/@visactor/vtable/1148-feature-listtable-tree-hierarchyState_2024-02-28-10-48.json deleted file mode 100644 index 535a1e2de..000000000 --- a/common/changes/@visactor/vtable/1148-feature-listtable-tree-hierarchyState_2024-02-28-10-48.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "refactor: setRecords support restoreHierarchyState #1148\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/1163-bug-customlayout-flex_2024-02-29-04-26.json b/common/changes/@visactor/vtable/1163-bug-customlayout-flex_2024-02-29-04-26.json deleted file mode 100644 index 38bd886cb..000000000 --- a/common/changes/@visactor/vtable/1163-bug-customlayout-flex_2024-02-29-04-26.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "fix: customlayout flex render error #1163\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/892-refactor-mousedown-not-stopPropagation_2024-01-10-09-31.json b/common/changes/@visactor/vtable/892-refactor-mousedown-not-stopPropagation_2024-01-10-09-31.json deleted file mode 100644 index 24b4c33a2..000000000 --- a/common/changes/@visactor/vtable/892-refactor-mousedown-not-stopPropagation_2024-01-10-09-31.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "refactor: vtable not stop event bubble #892", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/905-bug-tooltip-hide_2024-02-23-03-49.json b/common/changes/@visactor/vtable/905-bug-tooltip-hide_2024-02-23-03-49.json deleted file mode 100644 index 7d742548e..000000000 --- a/common/changes/@visactor/vtable/905-bug-tooltip-hide_2024-02-23-03-49.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "fix: when scroll tooltip hide #905\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-axis-innerOffset_2024-02-26-08-10.json b/common/changes/@visactor/vtable/fix-axis-innerOffset_2024-02-26-08-10.json deleted file mode 100644 index ae33139ee..000000000 --- a/common/changes/@visactor/vtable/fix-axis-innerOffset_2024-02-26-08-10.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: fix axis innerOffset", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-contextMenuItems-add-col-param-2024-02-29-08-05.json b/common/changes/@visactor/vtable/fix-contextMenuItems-add-col-param-2024-02-29-08-05.json deleted file mode 100644 index c0023cd06..000000000 --- a/common/changes/@visactor/vtable/fix-contextMenuItems-add-col-param-2024-02-29-08-05.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix-contextMenuItems-add-col-param", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-react-skip-func_2024-02-29-10-26.json b/common/changes/@visactor/vtable/fix-react-skip-func_2024-02-29-10-26.json deleted file mode 100644 index 4fb9ad5ec..000000000 --- a/common/changes/@visactor/vtable/fix-react-skip-func_2024-02-29-10-26.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "fix: add skipFunctionDiff in react-vtable", - "type": "none" - } - ], - "packageName": "@visactor/vtable" -} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/refactor-circular_dependency_2024-02-26-10-48.json b/common/changes/@visactor/vtable/refactor-circular_dependency_2024-02-26-10-48.json deleted file mode 100644 index 3a87ecd36..000000000 --- a/common/changes/@visactor/vtable/refactor-circular_dependency_2024-02-26-10-48.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "refactor: remove Circular dependency\n\n", - "type": "none", - "packageName": "@visactor/vtable" - } - ], - "packageName": "@visactor/vtable", - "email": "892739385@qq.com" -} \ No newline at end of file diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index f2e90f606..a2147593e 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -1 +1 @@ -[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"0.20.0","mainProject":"@visactor/vtable","nextBump":"minor"}] +[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"0.20.1","mainProject":"@visactor/vtable","nextBump":"patch"}] diff --git a/packages/react-vtable/package.json b/packages/react-vtable/package.json index cc27be3b3..71bddedf7 100644 --- a/packages/react-vtable/package.json +++ b/packages/react-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vtable", - "version": "0.20.0", + "version": "0.20.1", "description": "The react version of VTable", "keywords": [ "react", diff --git a/packages/vtable-editors/package.json b/packages/vtable-editors/package.json index e34714b92..70848d722 100644 --- a/packages/vtable-editors/package.json +++ b/packages/vtable-editors/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-editors", - "version": "0.20.0", + "version": "0.20.1", "description": "", "sideEffects": false, "main": "cjs/index.js", diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index dfb34ebce..a9cbd3bdc 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-export", - "version": "0.20.0", + "version": "0.20.1", "description": "The export util of VTable", "author": { "name": "VisActor", diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index c7dc6eb03..61f85bcc7 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -1,6 +1,51 @@ { "name": "@visactor/vtable", "entries": [ + { + "version": "0.20.1", + "tag": "@visactor/vtable_v0.20.1", + "date": "Thu, 29 Feb 2024 11:57:42 GMT", + "comments": { + "none": [ + { + "comment": "fix: hideColumnsSubheader with three levels show error #1105\n\n" + }, + { + "comment": "feat: add api getRecordIndexByCell #1121\n\n" + }, + { + "comment": "refactor: rename resize_column_end event arguments #1129\n\n" + }, + { + "comment": "refactor: api return value type\n\n" + }, + { + "comment": "refactor: setRecords support restoreHierarchyState #1148\n\n" + }, + { + "comment": "fix: customlayout flex render error #1163\n\n" + }, + { + "comment": "refactor: vtable not stop event bubble #892" + }, + { + "comment": "fix: when scroll tooltip hide #905\n\n" + }, + { + "comment": "fix: fix axis innerOffset" + }, + { + "comment": "fix-contextMenuItems-add-col-param" + }, + { + "comment": "fix: add skipFunctionDiff in react-vtable" + }, + { + "comment": "refactor: remove Circular dependency\n\n" + } + ] + } + }, { "version": "0.20.0", "tag": "@visactor/vtable_v0.20.0", diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index f600d2d7f..0a595defb 100644 --- a/packages/vtable/CHANGELOG.md +++ b/packages/vtable/CHANGELOG.md @@ -1,6 +1,40 @@ # Change Log - @visactor/vtable -This log was last generated on Fri, 23 Feb 2024 10:06:24 GMT and should not be manually modified. +This log was last generated on Thu, 29 Feb 2024 11:57:42 GMT and should not be manually modified. + +## 0.20.1 +Thu, 29 Feb 2024 11:57:42 GMT + +### Updates + +- fix: hideColumnsSubheader with three levels show error #1105 + + +- feat: add api getRecordIndexByCell #1121 + + +- refactor: rename resize_column_end event arguments #1129 + + +- refactor: api return value type + + +- refactor: setRecords support restoreHierarchyState #1148 + + +- fix: customlayout flex render error #1163 + + +- refactor: vtable not stop event bubble #892 +- fix: when scroll tooltip hide #905 + + +- fix: fix axis innerOffset +- fix-contextMenuItems-add-col-param +- fix: add skipFunctionDiff in react-vtable +- refactor: remove Circular dependency + + ## 0.20.0 Fri, 23 Feb 2024 10:06:24 GMT diff --git a/packages/vtable/package.json b/packages/vtable/package.json index 558d753cf..81a9493b1 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable", - "version": "0.20.0", + "version": "0.20.1", "description": "canvas table width high performance", "keywords": [ "grid",