From 27dc11c6f1d101d7d2a91ce61325006b90ae11fa Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Wed, 26 Jun 2024 16:44:34 +0800 Subject: [PATCH 01/14] feat: reuse @visactor/vutils-extension --- common/config/rush/pnpm-lock.yaml | 9 + packages/vtable/package.json | 3 +- packages/vtable/src/ListTable.ts | 6 +- packages/vtable/src/PivotChart.ts | 2 +- packages/vtable/src/PivotTable.ts | 2 +- .../src/layout/chart-helper/get-chart-spec.ts | 2 +- packages/vtable/src/ts-types/table-engine.ts | 2 +- packages/vtable/src/ts-types/theme.ts | 1 - .../algorithm/binary-search.ts | 28 -- .../vutil-extension-temp/algorithm/index.ts | 1 - .../vtable/src/vutil-extension-temp/index.ts | 4 - .../vutil-extension-temp/spec/clone-deep.ts | 61 ----- .../src/vutil-extension-temp/spec/common.ts | 23 -- .../src/vutil-extension-temp/spec/index.ts | 3 - .../vutil-extension-temp/spec/merge-spec.ts | 130 ---------- .../transform/tick-data/config.ts | 2 - .../transform/tick-data/continuous.ts | 92 ------- .../transform/tick-data/discrete/linear.ts | 241 ------------------ .../tick-data/discrete/polar-angle.ts | 101 -------- .../transform/tick-data/index.ts | 27 -- .../transform/tick-data/interface.ts | 57 ----- .../transform/tick-data/util.ts | 182 ------------- .../tick-data/utils/polar-label-position.ts | 18 -- .../src/vutil-extension-temp/utils/index.ts | 3 - .../src/vutil-extension-temp/utils/object.ts | 51 ---- .../src/vutil-extension-temp/utils/polar.ts | 47 ---- .../src/vutil-extension-temp/utils/text.ts | 28 -- 27 files changed, 17 insertions(+), 1109 deletions(-) delete mode 100644 packages/vtable/src/vutil-extension-temp/algorithm/binary-search.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/algorithm/index.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/index.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/spec/clone-deep.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/spec/common.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/spec/index.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/spec/merge-spec.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/transform/tick-data/config.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/transform/tick-data/continuous.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/transform/tick-data/discrete/linear.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/transform/tick-data/discrete/polar-angle.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/transform/tick-data/index.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/transform/tick-data/interface.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/transform/tick-data/util.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/transform/tick-data/utils/polar-label-position.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/utils/index.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/utils/object.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/utils/polar.ts delete mode 100644 packages/vtable/src/vutil-extension-temp/utils/text.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 3a292093d..2e185a281 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -290,6 +290,7 @@ importers: '@visactor/vscale': ~0.18.1 '@visactor/vtable-editors': workspace:* '@visactor/vutils': ~0.18.9 + '@visactor/vutils-extension': ~1.11.5 '@vitejs/plugin-react': 3.1.0 axios: ^1.4.0 chai: 4.3.4 @@ -334,6 +335,7 @@ importers: '@visactor/vscale': 0.18.9 '@visactor/vtable-editors': link:../vtable-editors '@visactor/vutils': 0.18.9 + '@visactor/vutils-extension': 1.11.5 cssfontparser: 1.2.1 devDependencies: '@babel/core': 7.20.12 @@ -3817,6 +3819,13 @@ packages: '@visactor/vdataset': 0.18.9 '@visactor/vutils': 0.18.9 + /@visactor/vutils-extension/1.11.5: + resolution: {integrity: sha512-FDTFOsEB3AIsXJQJH8reH3FWfNpXkhvTPiaGez4OFy0i7iWBzN74PDvKZmSDImQCWgfVeo4KWz9Jvl3qZO1tSw==} + dependencies: + '@visactor/vdataset': 0.18.9 + '@visactor/vutils': 0.18.9 + dev: false + /@visactor/vutils/0.18.9: resolution: {integrity: sha512-+CPwBATTQUPtXQ0KVXFRz8SCwAY9m5aR9QmtsVqya+mgaay3moFaAPNTbdkLBuZM5ewRYVcv/3fsDxuH+NXfFg==} dependencies: diff --git a/packages/vtable/package.json b/packages/vtable/package.json index 5bd871b5c..b8f63dc70 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -56,6 +56,7 @@ "@visactor/vutils": "~0.18.9", "@visactor/vscale": "~0.18.1", "@visactor/vdataset": "~0.18.1", + "@visactor/vutils-extension": "~1.11.5", "cssfontparser": "^1.2.1" }, "devDependencies": { @@ -123,4 +124,4 @@ "url": "https://github.com/VisActor/VTable.git", "directory": "packages/vtable" } -} +} \ No newline at end of file diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index a069a4fd4..847857947 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -18,15 +18,13 @@ import type { } from './ts-types'; import { HierarchyState } from './ts-types'; import { SimpleHeaderLayoutMap } from './layout'; -import { isNumber, isObject, isValid } from '@visactor/vutils'; +import { isValid } from '@visactor/vutils'; import { _setDataSource, _setRecords, sortRecords } from './core/tableHelper'; import { BaseTable } from './core'; 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'; import { Env } from './tools/env'; -import { editor } from './register'; import * as editors from './edit/editors'; import { EditManeger } from './edit/edit-manager'; import { computeColWidth } from './scenegraph/layout/compute-col-width'; @@ -35,7 +33,7 @@ import { defaultOrderFn } from './tools/util'; import type { IEditor } from '@visactor/vtable-editors'; import type { ColumnData, ColumnDefine } from './ts-types/list-table/layout-map/api'; import { getCellRadioState, setCellRadioState } from './state/radio/radio'; -import { cloneDeepSpec } from '@vutils-extension'; +import { cloneDeepSpec } from '@visactor/vutils-extension'; import { setCellCheckboxState } from './state/checkbox/checkbox'; import { EmptyTip } from './components/empty-tip/empty-tip'; diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index 30b5d7b9a..02155b05d 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -42,7 +42,7 @@ import { Title } from './components/title/title'; import { Env } from './tools/env'; import { TABLE_EVENT_TYPE } from './core/TABLE_EVENT_TYPE'; import type { IndicatorData } from './ts-types/list-table/layout-map/api'; -import { cloneDeepSpec } from '@vutils-extension'; +import { cloneDeepSpec } from '@visactor/vutils-extension'; import type { ITreeLayoutHeadNode } from './layout/tree-helper'; import { DimensionTree, type LayouTreeNode } from './layout/tree-helper'; import { IndicatorDimensionKeyPlaceholder } from './tools/global'; diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 2e47ffbcc..9c23efa46 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -41,7 +41,7 @@ import { computeColWidth } from './scenegraph/layout/compute-col-width'; import { computeRowHeight } from './scenegraph/layout/compute-row-height'; import { isAllDigits } from './tools/util'; import type { IndicatorData } from './ts-types/list-table/layout-map/api'; -import { cloneDeepSpec } from '@vutils-extension'; +import { cloneDeepSpec } from '@visactor/vutils-extension'; import { parseColKeyRowKeyForPivotTable, supplementIndicatorNodesForCustomTree } from './layout/layout-helper'; import { EmptyTip } from './components/empty-tip/empty-tip'; export class PivotTable extends BaseTable implements PivotTableAPI { 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 ef8c37c08..18bbf1362 100644 --- a/packages/vtable/src/layout/chart-helper/get-chart-spec.ts +++ b/packages/vtable/src/layout/chart-helper/get-chart-spec.ts @@ -5,7 +5,7 @@ import { getAxisOption, getAxisRange } from './get-axis-config'; import { getAxisDomainRangeAndLabels } from './get-axis-domain'; import { getNewRangeToAlign } from './zero-align'; import type { IChartIndicator, IIndicator } from '../../ts-types'; -import { cloneDeepSpec } from '@vutils-extension'; +import { cloneDeepSpec } from '@visactor/vutils-extension'; const NO_AXISID_FRO_VTABLE = 'NO_AXISID_FRO_VTABLE'; diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index 8cff11ff9..d5411dcf8 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -22,7 +22,7 @@ import type { ITitleDefine } from './pivot-table'; import type { ColumnsDefine } from './list-table'; -import type { ICellAxisOption, ITableAxisOption } from './component/axis'; +import type { ITableAxisOption } from './component/axis'; import type { IEditor } from '@visactor/vtable-editors'; import type { ITextStyleOption } from '../body-helper/style'; import type { DataSource } from '../data'; diff --git a/packages/vtable/src/ts-types/theme.ts b/packages/vtable/src/ts-types/theme.ts index 7579e0a78..d52e983f2 100644 --- a/packages/vtable/src/ts-types/theme.ts +++ b/packages/vtable/src/ts-types/theme.ts @@ -2,7 +2,6 @@ import type { ColorsDef, LineDashsDef, LineWidthsDef, LineWidthsPropertyDefine, LineDashsPropertyDefine } from '.'; import type { CheckboxStyle, ITextStyleOption, RadioStyle } from './column/style'; import type { ColorPropertyDefine, ColorsPropertyDefine } from './style-define'; -import type { ColumnIconOption } from './icon'; import type { ICellAxisOption } from './component/axis'; import type { PopTipAttributes } from '@visactor/vrender-components'; // ****** Custom Theme ******* diff --git a/packages/vtable/src/vutil-extension-temp/algorithm/binary-search.ts b/packages/vtable/src/vutil-extension-temp/algorithm/binary-search.ts deleted file mode 100644 index fe7c09731..000000000 --- a/packages/vtable/src/vutil-extension-temp/algorithm/binary-search.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 二分靠近框架,返回数组中第一个大于等于目标值的数的索引 - * @param arr 数组 - * @param compareFn 比较函数,返回(当前值-目标值) - */ -export const binaryFuzzySearch = (arr: T[], compareFn: (value: T) => number) => { - return binaryFuzzySearchInNumberRange(0, arr.length, value => compareFn(arr[value])); -}; - -/** - * 二分靠近框架,返回数字区间中第一个大于等于目标值的数字 - * @param x1 区间上界 - * @param x2 区间下界(不包含) - * @param compareFn 比较函数,返回(当前值-目标值) - */ -export const binaryFuzzySearchInNumberRange = (x1: number, x2: number, compareFn: (value: number) => number) => { - let left = x1; - let right = x2; - while (left < right) { - const mid = Math.floor((left + right) / 2); - if (compareFn(mid) >= 0) { - right = mid; // 第一个大于等于目标值的数 - } else { - left = mid + 1; - } - } - return left; -}; diff --git a/packages/vtable/src/vutil-extension-temp/algorithm/index.ts b/packages/vtable/src/vutil-extension-temp/algorithm/index.ts deleted file mode 100644 index e10f75227..000000000 --- a/packages/vtable/src/vutil-extension-temp/algorithm/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './binary-search'; diff --git a/packages/vtable/src/vutil-extension-temp/index.ts b/packages/vtable/src/vutil-extension-temp/index.ts deleted file mode 100644 index f9d6e21f1..000000000 --- a/packages/vtable/src/vutil-extension-temp/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './transform/tick-data'; -export * from './utils'; -export * from './algorithm'; -export * from './spec'; diff --git a/packages/vtable/src/vutil-extension-temp/spec/clone-deep.ts b/packages/vtable/src/vutil-extension-temp/spec/clone-deep.ts deleted file mode 100644 index 74aec2e98..000000000 --- a/packages/vtable/src/vutil-extension-temp/spec/clone-deep.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { isArray, isBoolean, isDate, isNumber, isString, isValid } from '@visactor/vutils'; -import { isDataView, isHTMLElement } from './common'; - -/** - * 深拷贝 spec,为避免循环引用,DataView 维持原有引用 - * @param spec 原spec - */ -export function cloneDeepSpec(spec: any, excludeKeys: string[] = ['data']) { - const value = spec; - - let result; - if (!isValid(value) || typeof value !== 'object') { - return value; - } - - // 判断是不是不能深拷贝的对象 - if (isDataView(value) || isHTMLElement(value)) { - return value; - } - - const isArr = isArray(value); - const length = value.length; - // 不考虑特殊数组的额外处理 - if (isArr) { - result = new Array(length); - } - // 不考虑 buffer / arguments 类型的处理以及 prototype 的额外处理 - else if (typeof value === 'object') { - result = {}; - } - // 不建议使用作为 Boolean / Number / String 作为构造器 - else if (isBoolean(value) || isNumber(value) || isString(value)) { - result = value; - } else if (isDate(value)) { - result = new Date(+value); - } - // 不考虑 ArrayBuffer / DataView / TypedArray / map / set / regexp / symbol 类型 - else { - result = undefined; - } - - // 不考虑 map / set / TypedArray 类型的赋值 - - // 不考虑对象的 symbol 属性 - const props = isArr ? undefined : Object.keys(Object(value)); - - let index = -1; - if (result) { - while (++index < (props || value).length) { - const key = props ? props[index] : index; - const subValue = value[key]; - if (excludeKeys?.includes(key.toString())) { - result[key] = subValue; - } else { - result[key] = cloneDeepSpec(subValue, excludeKeys); - } - } - } - - return result; -} diff --git a/packages/vtable/src/vutil-extension-temp/spec/common.ts b/packages/vtable/src/vutil-extension-temp/spec/common.ts deleted file mode 100644 index a11af544c..000000000 --- a/packages/vtable/src/vutil-extension-temp/spec/common.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { DataView } from '@visactor/vdataset'; - -export function isDataView(obj: any): obj is DataView { - return obj instanceof DataView; -} - -export function isHTMLElement(obj: any): obj is Element { - try { - return obj instanceof Element; - } catch { - // 跨端 plan B - const htmlElementKeys: (keyof Element)[] = [ - 'children', - 'innerHTML', - 'classList', - 'setAttribute', - 'tagName', - 'getBoundingClientRect' - ]; - const keys = Object.keys(obj); - return htmlElementKeys.every(key => keys.includes(key)); - } -} diff --git a/packages/vtable/src/vutil-extension-temp/spec/index.ts b/packages/vtable/src/vutil-extension-temp/spec/index.ts deleted file mode 100644 index 1245ec93d..000000000 --- a/packages/vtable/src/vutil-extension-temp/spec/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './clone-deep'; -export * from './common'; -export * from './merge-spec'; diff --git a/packages/vtable/src/vutil-extension-temp/spec/merge-spec.ts b/packages/vtable/src/vutil-extension-temp/spec/merge-spec.ts deleted file mode 100644 index b7d3a8433..000000000 --- a/packages/vtable/src/vutil-extension-temp/spec/merge-spec.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { isArray, isArrayLike, isObject, isPlainObject, isValid } from '@visactor/vutils'; - -function baseMerge(target: any, source: any, shallowArray = false) { - if (source) { - if (target === source) { - return; - } - if (isValid(source) && typeof source === 'object') { - // baseFor - const iterable = Object(source); - const props = []; - // keysIn - for (const key in iterable) { - props.push(key); - } - let { length } = props; - let propIndex = -1; - while (length--) { - const key = props[++propIndex]; - if ( - isValid(iterable[key]) && - typeof iterable[key] === 'object' && - !isArray(target[key]) // VChart 特有逻辑 - ) { - baseMergeDeep(target, source, key, shallowArray); - } else { - assignMergeValue(target, key, iterable[key]); - } - } - } - } -} - -// 由于目前 VChart 内部对 spec 会先执行一次深拷贝,merge 暂时不考虑 source 中有环的问题 -function baseMergeDeep(target: object, source: object, key: string, shallowArray = false) { - const objValue = target[key]; - const srcValue = source[key]; - let newValue = source[key]; - let isCommon = true; - // 不考虑 buffer / typedArray 类型 - if (isArray(srcValue)) { - if (shallowArray) { - // 依据参数对数组做浅拷贝 - newValue = []; - } else if (isArray(objValue)) { - newValue = objValue; - } else if (isArrayLike(objValue)) { - // 如果 source 为数组,则 target 的 arrayLike 对象也视作为数组处理 - newValue = new Array(objValue.length); - let index = -1; - const length = objValue.length; - while (++index < length) { - newValue[index] = objValue[index]; - } - } - } - // else if (isArray(srcValue) && shallowArray) { - // newValue = []; - // } - // 不考虑 argument 类型 - else if (isPlainObject(srcValue)) { - newValue = objValue ?? {}; - // 不考虑 prototype 的额外处理 - if (typeof objValue === 'function' || typeof objValue !== 'object') { - newValue = {}; - } - } else { - isCommon = false; - } - // 对 class 等复杂对象或者浅拷贝的 array 不做拷贝处理 - if (isCommon) { - baseMerge(newValue, srcValue, shallowArray); - } - assignMergeValue(target, key, newValue); -} - -function assignMergeValue(target: object, key: string, value: any) { - if ((value !== undefined && !eq(target[key], value)) || (value === undefined && !(key in target))) { - // 不考虑 __proto__ 的赋值处理 - target[key] = value; - } -} - -function eq(value: any, other: any) { - return value === other || (Number.isNaN(value) && Number.isNaN(other)); -} - -/* 与原生的 lodash merge 差异在于对数组是否应用最后一个 source 的结果 - * 以及对一些特殊情况的处理,比如对数组类型 padding 和对象类型的 padding 的 merge - */ -export function mergeSpec(target: any, ...sources: any[]): any { - let sourceIndex = -1; - const length = sources.length; - while (++sourceIndex < length) { - const source = sources[sourceIndex]; - baseMerge(target, source, true); - } - return target; -} - -export function mergeSpecWithFilter( - target: any, - filter: string | { type: string; index: number }, - spec: any, - forceMerge: boolean -) { - Object.keys(target).forEach(k => { - if (isObject(filter)) { - if (filter.type === k) { - if (isArray(target[k])) { - if (target[k].length >= filter.index) { - target[k][filter.index] = forceMerge ? mergeSpec({}, target[k][filter.index], spec) : spec; - } - } else { - target[k] = forceMerge ? mergeSpec({}, target[k], spec) : spec; - } - } - } else { - // filter === user id - if (isArray(target[k])) { - const index = target[k].findIndex((_s: { id: string | number }) => _s.id === filter); - if (index >= 0) { - target[k][index] = forceMerge ? mergeSpec({}, target[k][index], spec) : spec; - } - } else if (target.id === filter) { - target[k] = forceMerge ? mergeSpec({}, target[k], spec) : spec; - } - } - }); -} diff --git a/packages/vtable/src/vutil-extension-temp/transform/tick-data/config.ts b/packages/vtable/src/vutil-extension-temp/transform/tick-data/config.ts deleted file mode 100644 index af2881fac..000000000 --- a/packages/vtable/src/vutil-extension-temp/transform/tick-data/config.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** 连续轴默认 tick 数量 */ -export const DEFAULT_CONTINUOUS_TICK_COUNT = 5; diff --git a/packages/vtable/src/vutil-extension-temp/transform/tick-data/continuous.ts b/packages/vtable/src/vutil-extension-temp/transform/tick-data/continuous.ts deleted file mode 100644 index 981390e38..000000000 --- a/packages/vtable/src/vutil-extension-temp/transform/tick-data/continuous.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { LinearScale, ContinuousScale } from '@visactor/vscale'; -// eslint-disable-next-line no-duplicate-imports -import { isContinuous } from '@visactor/vscale'; -import { isFunction, isValid, last } from '@visactor/vutils'; -import { DEFAULT_CONTINUOUS_TICK_COUNT } from './config'; -import type { ICartesianTickDataOpt, ITickData, ITickDataOpt } from './interface'; -import type { ILabelItem } from './util'; -// eslint-disable-next-line no-duplicate-imports -import { convertDomainToTickData, getCartesianLabelBounds, hasOverlap, intersect } from './util'; - -/** - * 对于连续轴: - * - 如果spec配了tickCount、forceTickCount、tickStep,则直接输出LinearScale的ticks()、forceTicks()、stepTicks()结果; - * - 默认输出tickCount为10的ticks()结果。 - * - * @param scale - * @param op - * @returns - */ -export const continuousTicks = (scale: ContinuousScale, op: ITickDataOpt): ITickData[] => { - if (!isContinuous(scale.type)) { - return convertDomainToTickData(scale.domain()); - } - // if range is so small - const range = scale.range(); - const rangeSize = Math.abs(range[range.length - 1] - range[0]); - if (rangeSize < 2) { - return convertDomainToTickData([scale.domain()[0]]); - } - - const { tickCount, forceTickCount, tickStep, noDecimals = false, labelStyle } = op; - - let scaleTicks: number[]; - if (isValid(tickStep)) { - scaleTicks = (scale as LinearScale).stepTicks(tickStep); - } else if (isValid(forceTickCount)) { - scaleTicks = (scale as LinearScale).forceTicks(forceTickCount); - } else if (op.tickMode === 'd3') { - const count = isFunction(tickCount) ? tickCount({ axisLength: rangeSize, labelStyle }) : tickCount; - scaleTicks = (scale as LinearScale).d3Ticks(count ?? DEFAULT_CONTINUOUS_TICK_COUNT, { noDecimals }); - } else { - const count = isFunction(tickCount) ? tickCount({ axisLength: rangeSize, labelStyle }) : tickCount; - scaleTicks = (scale as LinearScale).ticks(count ?? DEFAULT_CONTINUOUS_TICK_COUNT, { noDecimals }); - } - - if (op.sampling) { - // 判断重叠 - if (op.coordinateType === 'cartesian' || (op.coordinateType === 'polar' && op.axisOrientType === 'radius')) { - const { labelGap = 4, labelFlush } = op as ICartesianTickDataOpt; - let items = getCartesianLabelBounds(scale, scaleTicks, op as ICartesianTickDataOpt).map( - (bounds, i) => - ({ - AABBBounds: bounds, - value: scaleTicks[i] - } as ILabelItem) - ); - while (items.length >= 3 && hasOverlap(items, labelGap)) { - items = methods.parity(items); - } - const ticks = items.map(item => item.value); - - if (ticks.length < 3 && labelFlush) { - if (ticks.length > 1) { - ticks.pop(); - } - if (last(ticks) !== last(scaleTicks)) { - ticks.push(last(scaleTicks)); - } - } - - scaleTicks = ticks; - } - } - - return convertDomainToTickData(scaleTicks); -}; - -const methods = { - parity: function (items: ILabelItem[]) { - return items.filter((item, i) => i % 2 === 0); - }, - greedy: function (items: ILabelItem[], sep: number) { - let a: ILabelItem; - return items.filter((b, i) => { - if (!i || !intersect(a.AABBBounds, b.AABBBounds, sep)) { - a = b; - return true; - } - return false; - }); - } -}; diff --git a/packages/vtable/src/vutil-extension-temp/transform/tick-data/discrete/linear.ts b/packages/vtable/src/vutil-extension-temp/transform/tick-data/discrete/linear.ts deleted file mode 100644 index 83cce6834..000000000 --- a/packages/vtable/src/vutil-extension-temp/transform/tick-data/discrete/linear.ts +++ /dev/null @@ -1,241 +0,0 @@ -import type { BandScale, IBaseScale } from '@visactor/vscale'; -import { isFunction, isValid, maxInArray, minInArray } from '@visactor/vutils'; -import type { ICartesianTickDataOpt, ITickData } from '../interface'; -import { convertDomainToTickData, getCartesianLabelBounds, isAxisHorizontal } from '../util'; -import { binaryFuzzySearchInNumberRange } from '../../../algorithm'; - -/** x1, x2, length */ -type OneDimensionalBounds = [number, number, number]; - -const getOneDimensionalLabelBounds = ( - scale: IBaseScale, - domain: any[], - op: ICartesianTickDataOpt, - isHorizontal: boolean -): OneDimensionalBounds[] => { - const labelBoundsList = getCartesianLabelBounds(scale, domain, op); - return labelBoundsList.map(bounds => { - if (isHorizontal) { - return [bounds.x1, bounds.x2, bounds.width()]; - } - return [bounds.y1, bounds.y2, bounds.height()]; - }); -}; - -/** 判断两个 bounds 是否有重叠情况 */ -const boundsOverlap = (prevBounds: OneDimensionalBounds, nextBounds: OneDimensionalBounds, gap = 0): boolean => { - return Math.max(prevBounds[0], nextBounds[0]) - gap / 2 <= Math.min(prevBounds[1], nextBounds[1]) + gap / 2; -}; - -/** 判断两个不相交的 bounds 相隔的距离 */ -export const boundsDistance = (prevBounds: OneDimensionalBounds, nextBounds: OneDimensionalBounds): number => { - if (prevBounds[1] < nextBounds[0]) { - return nextBounds[0] - prevBounds[1]; - } else if (nextBounds[1] < prevBounds[0]) { - return prevBounds[0] - nextBounds[1]; - } - return 0; -}; - -/** - * 对于离散轴: - * - 如果spec配了tickCount、forceTickCount、tickStep,则直接输出BandScale的ticks()、forceTicks()、stepTicks()结果; - * - 估算所有轴label的宽度(或高度,在竖轴的情况下)并存为数组domainLengthList; - * - 通过循环来寻找最小的step,使:如果在这个step下采样,轴标签互不遮挡(此处用到domainLengthList和scale.range()); - * - 如果用户配置了spec.label.lastVisible,则处理右边界:强制采样最后一个tick数据,并删掉这个tick的label所覆盖的那些tick数据。 - * - * @param scale - * @param op - * @returns - */ -export const linearDiscreteTicks = (scale: BandScale, op: ICartesianTickDataOpt): ITickData[] => { - const domain = scale.domain(); - if (!domain.length) { - return []; - } - const { tickCount, forceTickCount, tickStep, labelGap = 4, axisOrientType, labelStyle } = op; - const isHorizontal = isAxisHorizontal(axisOrientType); - const range = scale.range(); - - // if range is so small - const rangeSize = scale.calculateWholeRangeSize(); - if (rangeSize < 2) { - if (op.labelLastVisible) { - return convertDomainToTickData([domain[domain.length - 1]]); - } - return convertDomainToTickData([domain[0]]); - } - - let scaleTicks; - if (isValid(tickStep)) { - scaleTicks = scale.stepTicks(tickStep); - } else if (isValid(forceTickCount)) { - scaleTicks = scale.forceTicks(forceTickCount); - } else if (isValid(tickCount)) { - const count = isFunction(tickCount) ? tickCount({ axisLength: rangeSize, labelStyle }) : tickCount; - scaleTicks = scale.ticks(count); - } else if (op.sampling) { - const fontSize = (op.labelStyle.fontSize ?? 12) + 2; - const rangeStart = minInArray(range); - const rangeEnd = maxInArray(range); - - if (domain.length <= rangeSize / fontSize) { - const incrementUnit = (rangeEnd - rangeStart) / domain.length; - const labelBoundsList = getOneDimensionalLabelBounds(scale, domain, op, isHorizontal); - const minBoundsLength = Math.min(...labelBoundsList.map(bounds => bounds[2])); - - const stepResult = getStep( - domain, - labelBoundsList, - labelGap, - op.labelLastVisible, - Math.floor(minBoundsLength / incrementUnit), // 给step赋上合适的初值,有效改善外层循环次数 - false - ); - - scaleTicks = (scale as BandScale).stepTicks(stepResult.step); - if (op.labelLastVisible) { - if (stepResult.delCount) { - scaleTicks = scaleTicks.slice(0, scaleTicks.length - stepResult.delCount); - } - scaleTicks.push(domain[domain.length - 1]); - } - } else { - // only check first middle last, use the max size to sampling - const tempDomain = [domain[0], domain[Math.floor(domain.length / 2)], domain[domain.length - 1]]; - const tempList = getOneDimensionalLabelBounds(scale, tempDomain, op, isHorizontal); - let maxBounds: OneDimensionalBounds = null; - tempList.forEach(current => { - if (!maxBounds) { - maxBounds = current; - return; - } - if (maxBounds[2] < current[2]) { - maxBounds = current; - } - }); - - const step = - rangeEnd - rangeStart - labelGap > 0 - ? Math.ceil((domain.length * (labelGap + maxBounds[2])) / (rangeEnd - rangeStart - labelGap)) - : domain.length - 1; - - scaleTicks = (scale as BandScale).stepTicks(step); - - if ( - op.labelLastVisible && - (!scaleTicks.length || scaleTicks[scaleTicks.length - 1] !== domain[domain.length - 1]) - ) { - if ( - scaleTicks.length && - Math.abs(scale.scale(scaleTicks[scaleTicks.length - 1]) - scale.scale(domain[domain.length - 1])) < - maxBounds[2] - ) { - scaleTicks = scaleTicks.slice(0, -1); - } - scaleTicks.push(domain[domain.length - 1]); - } - } - } else { - scaleTicks = scale.domain(); - } - - return convertDomainToTickData(scaleTicks); -}; - -/** 计算合适的step */ -const getStep = ( - domain: any[], - labelBoundsList: OneDimensionalBounds[], - labelGap: number, - labelLastVisible: boolean, - defaultStep: number, - areAllBoundsSame: boolean -) => { - let resultDelCount = 0; - let resultStep = 0; - let resultTickCount = -1; - let minDiff = Number.MAX_VALUE; - - /** 验证在当前 step 下是否会产生重叠 */ - const validateStep = (step: number) => { - let success = true; - let ptr = 0; - do { - if (ptr + step < domain.length && boundsOverlap(labelBoundsList[ptr], labelBoundsList[ptr + step], labelGap)) { - success = false; - } - ptr += step; - } while (success && ptr < domain.length); - return success; - }; - - // 通过二分来寻找最小的step,使:如果在这个step下采样,轴标签互不遮挡 - const minValidStep = binaryFuzzySearchInNumberRange(defaultStep, domain.length, step => - validateStep(step) ? 1 : -1 - ); - - // 对 step 进行微调 - let step = minValidStep; - do { - if (step > minValidStep && !areAllBoundsSame) { - if (!validateStep(step)) { - step++; - continue; - } - } - if (labelLastVisible) { - const lastIndex = domain.length - 1; - let delCount = 0; - let ptr; - if (domain.length % step > 0) { - ptr = domain.length - (domain.length % step) + step; - } else { - ptr = domain.length; - } - do { - ptr -= step; // 获取最后一个label位置 - if (ptr === lastIndex || boundsOverlap(labelBoundsList[ptr], labelBoundsList[lastIndex], labelGap)) { - delCount++; - } else { - break; - } - } while (ptr > 0); - if (ptr === lastIndex) { - // 采到的最后的一个 label 刚好是最后一项,直接退出 - resultStep = step; - resultDelCount = delCount; - break; - } else { - // 尝试获取最均匀的结果,防止倒数第二项和最后一项有大的空档 - const tickCount = Math.floor(domain.length / step) - delCount + 1; - if (tickCount < resultTickCount) { - break; - } else { - resultTickCount = tickCount; - const distance1 = boundsDistance(labelBoundsList[ptr], labelBoundsList[lastIndex]); // 倒数第2项和最后一项的距离 - const distance2 = - ptr - step >= 0 ? boundsDistance(labelBoundsList[ptr - step], labelBoundsList[ptr]) : distance1; // 倒数第3项和倒数第2项的距离 - const diff = Math.abs(distance1 - distance2); - if (diff < minDiff) { - minDiff = diff; - resultStep = step; // 记录最均匀的 step - resultDelCount = delCount; - } - if (distance1 <= distance2) { - break; - } - } - } - } else { - resultStep = step; - break; - } - step++; - } while (step <= domain.length); - - return { - step: resultStep, - delCount: resultDelCount - }; -}; diff --git a/packages/vtable/src/vutil-extension-temp/transform/tick-data/discrete/polar-angle.ts b/packages/vtable/src/vutil-extension-temp/transform/tick-data/discrete/polar-angle.ts deleted file mode 100644 index 361072dda..000000000 --- a/packages/vtable/src/vutil-extension-temp/transform/tick-data/discrete/polar-angle.ts +++ /dev/null @@ -1,101 +0,0 @@ -import type { BandScale } from '@visactor/vscale'; -import { isFunction, isValid, maxInArray, minInArray } from '@visactor/vutils'; -import type { IPolarTickDataOpt, ITickData } from '../interface'; -import { convertDomainToTickData, getPolarAngleLabelBounds, labelOverlap } from '../util'; -import type { AABBBounds } from '@visactor/vutils'; - -/** - * 对于离散轴: - * - 如果spec配了tickCount、forceTickCount、tickStep,则直接输出BandScale的ticks()、forceTicks()、stepTicks()结果; - * - 估算所有轴label的宽高并存为数组labelBoundsList; - * - 通过循环来寻找最小的step,使:如果在这个step下采样,轴标签互不遮挡(此处用到labelBoundsList和scale.range()); - * - * @param scale - * @param op - * @returns - */ -export const polarAngleAxisDiscreteTicks = (scale: BandScale, op: IPolarTickDataOpt): ITickData[] => { - const { tickCount, forceTickCount, tickStep, getRadius, labelOffset, labelGap = 0, labelStyle } = op; - const radius = getRadius?.(); - if (!radius) { - return convertDomainToTickData(scale.domain()); - } - - let scaleTicks; - if (isValid(tickStep)) { - scaleTicks = scale.stepTicks(tickStep); - } else if (isValid(forceTickCount)) { - scaleTicks = scale.forceTicks(forceTickCount); - } else if (isValid(tickCount)) { - const range = scale.range(); - const rangeSize = Math.abs(range[range.length - 1] - range[0]); - const count = isFunction(tickCount) ? tickCount({ axisLength: rangeSize, labelStyle }) : tickCount; - scaleTicks = scale.ticks(count); - } else if (op.sampling) { - const domain = scale.domain(); - const range = scale.range(); - - const labelBoundsList = getPolarAngleLabelBounds(scale, domain, op); - - const rangeStart = minInArray(range); - const rangeEnd = maxInArray(range); - - const axisLength = Math.abs(rangeEnd - rangeStart) * (radius + labelOffset); - const incrementUnit = axisLength / domain.length; - const { step, delCount } = getStep( - domain, - labelBoundsList, - labelGap, - Math.floor( - labelBoundsList.reduce((min, curBounds) => { - return Math.min(min, curBounds.width(), curBounds.height()); - }, Number.MAX_VALUE) / incrementUnit - ) // 给step赋上合适的初值,有效改善外层循环次数 - ); - - scaleTicks = (scale as BandScale).stepTicks(step); - scaleTicks = scaleTicks.slice(0, scaleTicks.length - delCount); - } else { - scaleTicks = scale.domain(); - } - - return convertDomainToTickData(scaleTicks); -}; - -/** 计算合适的step */ -const getStep = (domain: any[], labelBoundsList: AABBBounds[], labelGap: number, defaultStep: number) => { - let step = defaultStep; - // 通过循环来寻找最小的step,使:如果在这个step下采样,轴标签互不遮挡 - do { - let success = true; - step++; - let ptr = 0; - do { - if (ptr + step < domain.length && labelOverlap(labelBoundsList[ptr], labelBoundsList[ptr + step], labelGap)) { - success = false; - } - ptr += step; - } while (success && ptr < domain.length); - if (success) { - break; - } - } while (step <= domain.length); - - let delCount = 0; - if (domain.length > 2) { - let ptr = domain.length - (domain.length % step); - if (ptr >= domain.length) { - ptr -= step; - } - // 判断首尾是否互相覆盖 - while (ptr > 0 && labelOverlap(labelBoundsList[0], labelBoundsList[ptr])) { - delCount++; - ptr -= step; - } - } - - return { - step, - delCount - }; -}; diff --git a/packages/vtable/src/vutil-extension-temp/transform/tick-data/index.ts b/packages/vtable/src/vutil-extension-temp/transform/tick-data/index.ts deleted file mode 100644 index 549fb07f7..000000000 --- a/packages/vtable/src/vutil-extension-temp/transform/tick-data/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { BandScale, ContinuousScale, IBaseScale } from '@visactor/vscale'; -// eslint-disable-next-line no-duplicate-imports -import { isContinuous, isDiscrete } from '@visactor/vscale'; -import { continuousTicks } from './continuous'; -import { linearDiscreteTicks } from './discrete/linear'; -import { polarAngleAxisDiscreteTicks } from './discrete/polar-angle'; -import type { ICartesianTickDataOpt, IPolarTickDataOpt, ITickData, ITickDataOpt } from './interface'; -import { convertDomainToTickData } from './util'; - -export * from './interface'; -export { convertDomainToTickData }; - -// 总入口 -export const ticks = (scale: IBaseScale, op: ITickDataOpt): ITickData[] => { - if (isContinuous(scale.type)) { - return continuousTicks(scale as ContinuousScale, op); - } else if (isDiscrete(scale.type)) { - if (op.coordinateType === 'cartesian') { - return linearDiscreteTicks(scale as BandScale, op as ICartesianTickDataOpt); - } else if (op.coordinateType === 'polar') { - if (op.axisOrientType === 'angle') { - return polarAngleAxisDiscreteTicks(scale as BandScale, op as IPolarTickDataOpt); - } - } - } - return convertDomainToTickData(scale.domain()); -}; diff --git a/packages/vtable/src/vutil-extension-temp/transform/tick-data/interface.ts b/packages/vtable/src/vutil-extension-temp/transform/tick-data/interface.ts deleted file mode 100644 index 1c70971cd..000000000 --- a/packages/vtable/src/vutil-extension-temp/transform/tick-data/interface.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { ITextGraphicAttribute } from '@visactor/vrender-core'; - -export type CoordinateType = 'cartesian' | 'polar' | 'geo' | 'none'; -export type IOrientType = 'left' | 'top' | 'right' | 'bottom' | 'z'; -export type IPolarOrientType = 'radius' | 'angle'; - -export interface ITickDataOpt { - /** - * 是否进行轴采样 - */ - sampling?: boolean; - tickCount?: number | ((option: ITickCallbackOption) => number); - forceTickCount?: number; - tickStep?: number; - tickMode?: 'average' | 'd3' | string; - noDecimals?: boolean; - - coordinateType: CoordinateType; - axisOrientType: IOrientType | IPolarOrientType; - startAngle?: number; - - labelFormatter?: (value: any) => string; - labelStyle: ITextGraphicAttribute; - labelGap?: number; -} - -export interface ICartesianTickDataOpt extends ITickDataOpt { - axisOrientType: IOrientType; - labelLastVisible: boolean; - labelFlush: boolean; -} - -export interface IPolarTickDataOpt extends ITickDataOpt { - axisOrientType: IPolarOrientType; - getRadius: () => number; - labelOffset: number; - inside: boolean; -} - -export interface ITickData { - index: number; - value: number | string; - // label: string; -} - -type ITickCallbackOption = { - /** - * 坐标轴占据的画布大小。 - * 直角坐标系中为轴的宽度或高度。 - * 极坐标系中半径轴的长度。 - */ - axisLength?: number; - /** - * 轴标签的样式 - */ - labelStyle?: ITextGraphicAttribute; -}; diff --git a/packages/vtable/src/vutil-extension-temp/transform/tick-data/util.ts b/packages/vtable/src/vutil-extension-temp/transform/tick-data/util.ts deleted file mode 100644 index abc95029a..000000000 --- a/packages/vtable/src/vutil-extension-temp/transform/tick-data/util.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { IBaseScale } from '@visactor/vscale'; -import type { IBoundsLike } from '@visactor/vutils'; -// eslint-disable-next-line no-duplicate-imports -import { AABBBounds, degreeToRadian } from '@visactor/vutils'; -import type { IGraphic, TextAlignType, TextBaselineType } from '@visactor/vrender-core'; -import { initTextMeasure } from '../../utils/text'; -import type { ICartesianTickDataOpt, IOrientType, IPolarTickDataOpt, ITickData } from './interface'; -import { getLabelPosition } from './utils/polar-label-position'; - -export const convertDomainToTickData = (domain: any[]): ITickData[] => { - const ticks = domain.map((t: number, index: number) => { - return { - index, - value: t - }; - }); - return ticks; -}; - -/** 判断两个label是否有重叠情况 */ -export const labelOverlap = (prevLabel: AABBBounds, nextLabel: AABBBounds, gap = 0): boolean => { - const prevBounds = new AABBBounds(prevLabel).expand(gap / 2); - const nextBounds = new AABBBounds(nextLabel).expand(gap / 2); - return prevBounds.intersects(nextBounds); -}; - -/** 判断两个不相交的label相隔的距离 */ -export const labelDistance = (prevLabel: AABBBounds, nextLabel: AABBBounds): [number, number] => { - let horizontal = 0; - if (prevLabel.x2 < nextLabel.x1) { - horizontal = nextLabel.x1 - prevLabel.x2; - } else if (nextLabel.x2 < prevLabel.x1) { - horizontal = prevLabel.x1 - nextLabel.x2; - } - - let vertical = 0; - if (prevLabel.y2 < nextLabel.y1) { - vertical = nextLabel.y1 - prevLabel.y2; - } else if (nextLabel.y2 < prevLabel.y1) { - vertical = prevLabel.y1 - nextLabel.y2; - } - - return [horizontal, vertical]; -}; - -export function intersect(a: IBoundsLike, b: IBoundsLike, sep: number) { - return sep > Math.max(b.x1 - a.x2, a.x1 - b.x2, b.y1 - a.y2, a.y1 - b.y2); -} - -export interface ILabelItem extends Pick { - value?: T; -} - -export function hasOverlap(items: ILabelItem[], pad: number): boolean { - for (let i = 1, n = items.length, a = items[0], b; i < n; a = b, ++i) { - b = items[i]; - if (intersect(a.AABBBounds, b.AABBBounds, pad)) { - return true; - } - } - return false; -} - -export const MIN_TICK_GAP = 12; - -export const getCartesianLabelBounds = (scale: IBaseScale, domain: any[], op: ICartesianTickDataOpt): AABBBounds[] => { - const { labelStyle, axisOrientType, labelFlush, labelFormatter, startAngle = 0 } = op; - let labelAngle = labelStyle.angle ?? 0; - if (labelStyle.direction === 'vertical') { - labelAngle += degreeToRadian(90); - } - const isHorizontal = ['bottom', 'top'].includes(axisOrientType); - const isVertical = ['left', 'right'].includes(axisOrientType); - let scaleX = 1; - let scaleY = 0; - if (isHorizontal) { - // nothing to update - } else if (isVertical) { - scaleX = 0; - scaleY = 1; - } else if (startAngle) { - scaleX = Math.cos(startAngle); - scaleY = -Math.sin(startAngle); - } - - const textMeasure = initTextMeasure(labelStyle); - const labelBoundsList = domain.map((v: any, i: number) => { - const str = labelFormatter ? labelFormatter(v) : `${v}`; - - // 估算文本宽高 - const { width, height } = textMeasure.quickMeasure(str); - const textWidth = Math.max(width, MIN_TICK_GAP); - const textHeight = Math.max(height, MIN_TICK_GAP); - - // 估算文本位置 - const pos = scale.scale(v); - const baseTextX = scaleX * pos; - const baseTextY = scaleY * pos; - let textX = baseTextX; - let textY = baseTextY; - - let align: TextAlignType; - if (labelFlush && isHorizontal && i === 0) { - align = 'left'; - } else if (labelFlush && isHorizontal && i === domain.length - 1) { - align = 'right'; - } else { - align = labelStyle.textAlign ?? 'center'; - } - if (align === 'right') { - textX -= textWidth; - } else if (align === 'center') { - textX -= textWidth / 2; - } - - let baseline: TextBaselineType; - if (labelFlush && isVertical && i === 0) { - baseline = 'top'; - } else if (labelFlush && isVertical && i === domain.length - 1) { - baseline = 'bottom'; - } else { - baseline = labelStyle.textBaseline ?? 'middle'; - } - if (baseline === 'bottom') { - textY -= textHeight; - } else if (baseline === 'middle') { - textY -= textHeight / 2; - } - - // 计算 label 包围盒 - const bounds = new AABBBounds().set(textX, textY, textX + textWidth, textY + textHeight); - - if (labelAngle) { - bounds.rotate(labelAngle, baseTextX, baseTextY); - } - - return bounds; - }); - - return labelBoundsList; -}; - -export const getPolarAngleLabelBounds = (scale: IBaseScale, domain: any[], op: IPolarTickDataOpt): AABBBounds[] => { - const { labelStyle, getRadius, labelOffset, labelFormatter, inside } = op; - const radius = getRadius?.(); - const labelAngle = labelStyle.angle ?? 0; - - const textMeasure = initTextMeasure(labelStyle); - const labelBoundsList = domain.map((v: any) => { - const str = labelFormatter ? labelFormatter(v) : `${v}`; - - // 估算文本宽高 - const { width, height } = textMeasure.quickMeasure(str); - const textWidth = Math.max(width, MIN_TICK_GAP); - const textHeight = Math.max(height, MIN_TICK_GAP); - - // 估算文本位置 - const angle = scale.scale(v); - let textX = 0; - let textY = 0; - const orient = { - align: labelStyle.textAlign ?? 'center', - baseline: labelStyle.textBaseline ?? 'middle' - }; - - const { x, y } = getLabelPosition(angle, { x: 0, y: 0 }, radius, labelOffset, inside, str, labelStyle); - textX = x + (orient.align === 'right' ? -textWidth : orient.align === 'center' ? -textWidth / 2 : 0); - textY = y + (orient.baseline === 'bottom' ? -textHeight : orient.baseline === 'middle' ? -textHeight / 2 : 0); - - // 计算 label 包围盒 - const bounds = new AABBBounds() - .set(textX, textY, textX + textWidth, textY + textHeight) - .rotate(labelAngle, textX + textWidth / 2, textY + textHeight / 2); - return bounds; - }); - - return labelBoundsList; -}; - -export const isAxisHorizontal = (axisOrientType: IOrientType) => { - return (['bottom', 'top', 'z'] as IOrientType[]).includes(axisOrientType); -}; diff --git a/packages/vtable/src/vutil-extension-temp/transform/tick-data/utils/polar-label-position.ts b/packages/vtable/src/vutil-extension-temp/transform/tick-data/utils/polar-label-position.ts deleted file mode 100644 index cef160c38..000000000 --- a/packages/vtable/src/vutil-extension-temp/transform/tick-data/utils/polar-label-position.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ITextGraphicAttribute } from '@visactor/vrender-core'; -import { getCircleLabelPosition, getCircleVerticalVector, getVerticalCoord } from '@visactor/vrender-components'; -import { polarToCartesian } from '@visactor/vutils'; - -export function getLabelPosition( - angle: number, - center: { x: number; y: number }, - radius: number, - labelOffset: number, - inside: boolean, - text: string | number, - style: Partial -) { - const point = polarToCartesian({ x: 0, y: 0 }, radius, angle); - const labelPoint = getVerticalCoord(point, getCircleVerticalVector(labelOffset, point, center, inside)); - const vector = getCircleVerticalVector(labelOffset || 1, labelPoint, center, inside); - return getCircleLabelPosition(labelPoint, vector, text, style); -} diff --git a/packages/vtable/src/vutil-extension-temp/utils/index.ts b/packages/vtable/src/vutil-extension-temp/utils/index.ts deleted file mode 100644 index 35641ed3a..000000000 --- a/packages/vtable/src/vutil-extension-temp/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './object'; -export * from './polar'; -export * from './text'; diff --git a/packages/vtable/src/vutil-extension-temp/utils/object.ts b/packages/vtable/src/vutil-extension-temp/utils/object.ts deleted file mode 100644 index 8798664d3..000000000 --- a/packages/vtable/src/vutil-extension-temp/utils/object.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { get, isArray, isFunction, isNil, isObject } from '@visactor/vutils'; - -/** - * 判断一个 spec 是否包含另一个 spec 片段 - * @param spec 原始 spec - * @param searchSpec 要匹配的 spec 片段 - */ -export const includeSpec = (spec: Partial, searchSpec: Partial): boolean => { - if (spec === searchSpec) { - return true; - } - if (isFunction(spec) || isFunction(searchSpec)) { - return false; - } - if (isArray(spec) && isArray(searchSpec)) { - return searchSpec.every(searchItem => spec.some(item => includeSpec(item, searchItem))); - } - if (isObject(spec) && isObject(searchSpec)) { - return Object.keys(searchSpec).every(key => includeSpec(spec[key], searchSpec[key])); - } - return false; -}; - -export const setProperty = (target: T, path: Array, value: any): T => { - if (isNil(path)) { - return target; - } - const key = path[0]; - if (isNil(key)) { - return target; - } - if (path.length === 1) { - target[key] = value; - return target; - } - if (isNil(target[key])) { - if (typeof path[1] === 'number') { - target[key] = []; - } else { - target[key] = {}; - } - } - return setProperty(target[key], path.slice(1), value); -}; - -export const getProperty = (target: any, path: Array, defaultValue?: T): T => { - if (isNil(path)) { - return undefined; - } - return get(target, path as string[], defaultValue) as T; -}; diff --git a/packages/vtable/src/vutil-extension-temp/utils/polar.ts b/packages/vtable/src/vutil-extension-temp/utils/polar.ts deleted file mode 100644 index c64ea2cf4..000000000 --- a/packages/vtable/src/vutil-extension-temp/utils/polar.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { TextAlignType, TextBaselineType } from '@visactor/vrender-core'; - -/** - * 角度标准化处理 - * @param angle 弧度角 - */ -export function normalizeAngle(angle: number): number { - while (angle < 0) { - angle += Math.PI * 2; - } - while (angle >= Math.PI * 2) { - angle -= Math.PI * 2; - } - return angle; -} - -/** - * 计算对应角度下的角度轴标签定位属性 - * @param angle 弧度角,需要注意是逆时针计算的 - * @returns - */ -export function angleLabelOrientAttribute(angle: number) { - let align: TextAlignType = 'center'; - let baseline: TextBaselineType = 'middle'; - - angle = normalizeAngle(angle); - - // left: 5/3 - 1/3; right: 2/3 - 4/3; center: 5/3 - 1/3 & 2/3 - 4/3 - if (angle >= Math.PI * (5 / 3) || angle <= Math.PI * (1 / 3)) { - align = 'left'; - } else if (angle >= Math.PI * (2 / 3) && angle <= Math.PI * (4 / 3)) { - align = 'right'; - } else { - align = 'center'; - } - - // bottom: 7/6 - 11/6; top: 1/6 - 5/6; middle: 11/6 - 1/6 & 5/6 - 7/6 - if (angle >= Math.PI * (7 / 6) && angle <= Math.PI * (11 / 6)) { - baseline = 'bottom'; - } else if (angle >= Math.PI * (1 / 6) && angle <= Math.PI * (5 / 6)) { - baseline = 'top'; - } else { - baseline = 'middle'; - } - - return { align, baseline }; -} diff --git a/packages/vtable/src/vutil-extension-temp/utils/text.ts b/packages/vtable/src/vutil-extension-temp/utils/text.ts deleted file mode 100644 index e39d0a183..000000000 --- a/packages/vtable/src/vutil-extension-temp/utils/text.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { ITextMeasureOption } from '@visactor/vutils'; -// eslint-disable-next-line no-duplicate-imports -import { TextMeasure } from '@visactor/vutils'; -import type { ITextGraphicAttribute } from '@visactor/vrender-core'; -import { getTextBounds } from '@visactor/vrender-core'; - -export const initTextMeasure = ( - textSpec?: Partial, - option?: Partial, - useNaiveCanvas?: boolean, - defaultFontParams?: Partial -): TextMeasure => { - return new TextMeasure( - { - defaultFontParams: { - fontFamily: - // eslint-disable-next-line max-len - 'PingFang SC,Helvetica Neue,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol', - fontSize: 14, - ...defaultFontParams - }, - getTextBounds: useNaiveCanvas ? undefined : getTextBounds, - specialCharSet: '-/: .,@%\'"~' + TextMeasure.ALPHABET_CHAR_SET + TextMeasure.ALPHABET_CHAR_SET.toUpperCase(), - ...(option ?? {}) - }, - textSpec - ); -}; From fbb7ca8a2ee30b52b06721c8b16561c95d789afd Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Wed, 26 Jun 2024 16:48:47 +0800 Subject: [PATCH 02/14] fix: remove unused vrender register --- packages/vtable/src/vrender.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/vtable/src/vrender.ts b/packages/vtable/src/vrender.ts index ad54c76f9..53581830d 100644 --- a/packages/vtable/src/vrender.ts +++ b/packages/vtable/src/vrender.ts @@ -40,23 +40,23 @@ export function registerForVrender() { loadNodeEnv(container); } registerArc(); - registerArc3d(); - registerArea(); + // registerArc3d(); + // registerArea(); registerCircle(); - registerGlyph(); + // registerGlyph(); registerGroup(); registerImage(); registerLine(); - registerPath(); - registerPolygon(); - registerPyramid3d(); + // registerPath(); + // registerPolygon(); + // registerPyramid3d(); registerRect(); - registerRect3d(); + // registerRect3d(); registerRichtext(); - registerShadowRoot(); + // registerShadowRoot(); registerSymbol(); registerText(); - registerWrapText(); + // registerWrapText(); } export { Direction } from '@visactor/vrender-core'; From 73a570aa39d43db967697f3c4acea4dae53bfb3e Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Wed, 26 Jun 2024 18:00:58 +0800 Subject: [PATCH 03/14] feat: move axis into factory --- packages/vtable/src/PivotChart.ts | 3 ++ packages/vtable/src/components/axis/axis.ts | 19 ++++++++++-- .../axis/get-axis-component-size.ts | 2 ++ packages/vtable/src/components/index.ts | 11 +++++++ packages/vtable/src/core/BaseTable.ts | 8 ++--- packages/vtable/src/core/factory.ts | 29 +++++++++++++++++++ .../layout/chart-helper/get-axis-config.ts | 3 +- .../vtable/src/layout/pivot-header-layout.ts | 6 ++-- .../scenegraph/group-creater/cell-helper.ts | 10 +++---- .../group-creater/cell-type/checkbox-cell.ts | 6 ++-- .../scenegraph/layout/compute-col-width.ts | 7 +++-- .../scenegraph/layout/compute-row-height.ts | 8 +++-- .../src/scenegraph/layout/update-width.ts | 4 ++- .../scenegraph/refresh-node/update-chart.ts | 4 ++- packages/vtable/src/ts-types/base-table.ts | 2 +- 15 files changed, 92 insertions(+), 30 deletions(-) create mode 100644 packages/vtable/src/components/index.ts create mode 100644 packages/vtable/src/core/factory.ts diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index 02155b05d..28890b44e 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -49,6 +49,9 @@ import { IndicatorDimensionKeyPlaceholder } from './tools/global'; import { checkHasCartesianChart } from './layout/chart-helper/get-chart-spec'; import { supplementIndicatorNodesForCustomTree } from './layout/layout-helper'; import { EmptyTip } from './components/empty-tip/empty-tip'; +import { registerAxis } from './components'; + +// registerAxis(); export class PivotChart extends BaseTable implements PivotChartAPI { layoutNodeId: { seqId: number } = { seqId: 0 }; declare internalProps: PivotChartProtected; diff --git a/packages/vtable/src/components/axis/axis.ts b/packages/vtable/src/components/axis/axis.ts index 3dcf9b23e..a796fc6d4 100644 --- a/packages/vtable/src/components/axis/axis.ts +++ b/packages/vtable/src/components/axis/axis.ts @@ -2,15 +2,15 @@ import { degreeToRadian, isNil, isValidNumber, merge } from '@visactor/vutils'; import type { BaseTableAPI } from '../../ts-types/base-table'; import type { ICellAxisOption } from '../../ts-types/component/axis'; import { LineAxis, type LineAxisAttributes } from '@visactor/vrender-components'; -import { commonAxis, getAxisAttributes, getCommonAxis } from './get-axis-attributes'; +import { getAxisAttributes, getCommonAxis } from './get-axis-attributes'; import { isXAxis, isYAxis } from '../util/orient'; import type { IOrientType } from '../../ts-types/component/util'; import { BandAxisScale } from './band-scale'; import { registerDataSetInstanceParser, registerDataSetInstanceTransform } from '../util/register'; import type { Parser } from '@visactor/vdataset'; -import { DataView } from '@visactor/vdataset'; +import { DataSet, DataView } from '@visactor/vdataset'; import type { IBaseScale } from '@visactor/vscale'; -import { ticks } from '@vutils-extension'; +import { ticks } from '@src/vrender'; import { LinearAxisScale } from './linear-scale'; import { doOverlap } from './label-overlap'; import type { TableTheme } from '../../themes/theme'; @@ -21,6 +21,16 @@ const scaleParser: Parser = (scale: IBaseScale) => { return scale; }; +export interface ICartesianAxis { + new ( + option: ICellAxisOption, + width: number, + height: number, + padding: [number, number, number, number], + table: BaseTableAPI + ): CartesianAxis; +} + export class CartesianAxis { width: number; height: number; @@ -118,6 +128,9 @@ export class CartesianAxis { const label = this.option.label || {}; const tick = this.option.tick || {}; + if (!this.table._vDataSet) { + this.table._vDataSet = new DataSet(); + } const tickData = new DataView(this.table._vDataSet) .parse(this.scale._scale, { type: 'scale' diff --git a/packages/vtable/src/components/axis/get-axis-component-size.ts b/packages/vtable/src/components/axis/get-axis-component-size.ts index 7e27f917f..3c11cac29 100644 --- a/packages/vtable/src/components/axis/get-axis-component-size.ts +++ b/packages/vtable/src/components/axis/get-axis-component-size.ts @@ -3,6 +3,8 @@ import type { BaseTableAPI } from '../../ts-types/base-table'; import type { ICellAxisOption } from '../../ts-types/component/axis'; import { DEFAULT_TEXT_FONT_FAMILY, DEFAULT_TEXT_FONT_SIZE, commonAxis } from './get-axis-attributes'; +export type ComputeAxisComponentWidth = (config: ICellAxisOption, table: BaseTableAPI) => number; +export type ComputeAxisComponentHeight = (config: ICellAxisOption, table: BaseTableAPI) => number; /** * @description: compuational vertical axis width * @param {ICellAxisOption} config diff --git a/packages/vtable/src/components/index.ts b/packages/vtable/src/components/index.ts new file mode 100644 index 000000000..d9b0c0f39 --- /dev/null +++ b/packages/vtable/src/components/index.ts @@ -0,0 +1,11 @@ +import { Factory } from '../core/factory'; +import { getAxisConfigInPivotChart } from '../layout/chart-helper/get-axis-config'; +import { CartesianAxis } from './axis/axis'; +import { computeAxisComponentHeight, computeAxisComponentWidth } from './axis/get-axis-component-size'; + +export const registerAxis = () => { + Factory.registerComponent('axis', CartesianAxis); + Factory.registerFunction('computeAxisComponentWidth', computeAxisComponentWidth); + Factory.registerFunction('computeAxisComponentHeight', computeAxisComponentHeight); + Factory.registerFunction('getAxisConfigInPivotChart', getAxisConfigInPivotChart); +}; diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 5f8166725..ccb26ba9d 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -116,7 +116,7 @@ import type { import { FocusInput } from './FouseInput'; import { defaultPixelRatio } from '../tools/pixel-ratio'; import { createLegend } from '../components/legend/create-legend'; -import { DataSet } from '@visactor/vdataset'; +import type { DataSet } from '@visactor/vdataset'; import { Title } from '../components/title/title'; import type { Chart } from '../scenegraph/graphic/chart'; import { setBatchRenderChartCount } from '../scenegraph/graphic/contributions/chart-render-helper'; @@ -159,7 +159,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { canvasWidth?: number; canvasHeight?: number; - _vDataSet: DataSet; + _vDataSet?: DataSet; scenegraph: Scenegraph; stateManager: StateManager; eventManager: EventManager; @@ -405,7 +405,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { : 0 : 10; // 生成scenegraph - this._vDataSet = new DataSet(); + // this._vDataSet = new DataSet(); this.scenegraph = new Scenegraph(this); this.stateManager = new StateManager(this); this.eventManager = new EventManager(this); @@ -2283,7 +2283,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { : 0 : 10; // 生成scenegraph - this._vDataSet = new DataSet(); + // this._vDataSet = new DataSet(); internalProps.legends?.forEach(legend => { legend?.release(); }); diff --git a/packages/vtable/src/core/factory.ts b/packages/vtable/src/core/factory.ts new file mode 100644 index 000000000..91fa95eb7 --- /dev/null +++ b/packages/vtable/src/core/factory.ts @@ -0,0 +1,29 @@ +export class Factory { + private static _components: { [key: string]: any } = {}; + private static _functions: { [key: string]: any } = {}; + private static _cellTypes: { [key: string]: any } = {}; + + static registerComponent(key: string, component: any) { + Factory._components[key] = component; + } + + static getComponent(key: string) { + return Factory._components[key]; + } + + static registerFunction(key: string, func: any) { + Factory._functions[key] = func; + } + + static getFunction(key: string) { + return Factory._functions[key]; + } + + static registerCellType(key: string, cellType: any) { + Factory._cellTypes[key] = cellType; + } + + static getCellType(key: string) { + return Factory._cellTypes[key]; + } +} diff --git a/packages/vtable/src/layout/chart-helper/get-axis-config.ts b/packages/vtable/src/layout/chart-helper/get-axis-config.ts index 6bf67ab11..d812ad46c 100644 --- a/packages/vtable/src/layout/chart-helper/get-axis-config.ts +++ b/packages/vtable/src/layout/chart-helper/get-axis-config.ts @@ -6,6 +6,7 @@ import { getAxisDomainRangeAndLabels } from './get-axis-domain'; import type { CollectedValue } from '../../ts-types'; import { getNewRangeToAlign } from './zero-align'; +export type GetAxisConfigInPivotChart = (col: number, row: number, layout: PivotHeaderLayoutMap) => any; export function getAxisConfigInPivotChart(col: number, row: number, layout: PivotHeaderLayoutMap): any { if (!layout._table.isPivotChart()) { return undefined; @@ -422,7 +423,7 @@ export function getAxisOption(col: number, row: number, orient: string, layout: }; } -export function checkZeroAlign(spec: any, orient: string, layout: PivotHeaderLayoutMap) { +function checkZeroAlign(spec: any, orient: string, layout: PivotHeaderLayoutMap) { // check condition: // 1. two axes and one set sync // 2. axisId in sync is another diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index 553fdbb34..e71093b8a 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -34,8 +34,6 @@ import type { PivotTable } from '../PivotTable'; import type { PivotChart } from '../PivotChart'; import { IndicatorDimensionKeyPlaceholder } from '../tools/global'; import { diffCellAddress } from '../tools/diff-cell'; -import type { ILinkDimension } from '../ts-types/pivot-table/dimension/link-dimension'; -import type { IImageDimension } from '../ts-types/pivot-table/dimension/image-dimension'; import { checkHasCartesianChart, checkHasChart, @@ -54,7 +52,8 @@ import { cloneDeep, isArray, isValid } from '@visactor/vutils'; import type { TextStyle } from '../body-helper/style'; import type { ITableAxisOption } from '../ts-types/component/axis'; import { getQuadProps } from '../scenegraph/utils/padding'; -import { getAxisConfigInPivotChart } from './chart-helper/get-axis-config'; +import type { GetAxisConfigInPivotChart } from './chart-helper/get-axis-config'; +import { Factory } from '../core/factory'; // export const sharedVar = { seqId: 0 }; // let colIndex = 0; @@ -2719,6 +2718,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { ((this.isFrozenRow(col, row) || this.isBottomFrozenRow(col, row)) && isHasCartesianChartInline(col, row, 'col', this)) ) { + const getAxisConfigInPivotChart = Factory.getFunction('getAxisConfigInPivotChart') as GetAxisConfigInPivotChart; return getAxisConfigInPivotChart(col, row, this); } 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 06a30fcdc..d58713507 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -8,7 +8,6 @@ import type { ColumnDefine, ColumnTypeOption, ImageColumnDefine, - MappingRule, ProgressbarColumnDefine, IRowSeriesNumber, TextColumnDefine, @@ -23,17 +22,15 @@ 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 { BaseTableAPI, HeaderData, PivotTableProtected } from '../../ts-types/base-table'; +import type { BaseTableAPI, HeaderData } from '../../ts-types/base-table'; import { getCellCornerRadius, getStyleTheme } from '../../core/tableHelper'; import { isPromise } from '../../tools/helper'; import { dealPromiseData } from '../utils/deal-promise-data'; -import { CartesianAxis } from '../../components/axis/axis'; +import type { ICartesianAxis } from '../../components/axis/axis'; +import { Factory } from '../../core/factory'; import { createCheckboxCellGroup } from './cell-type/checkbox-cell'; -// import type { PivotLayoutMap } from '../../layout/pivot-layout'; -import type { PivotHeaderLayoutMap } from '../../layout/pivot-header-layout'; 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'; import { breakString } from '../utils/break-string'; @@ -188,6 +185,7 @@ export function createCell( const axisConfig = table.internalProps.layoutMap.getAxisConfigInPivotChart(col, row); if (axisConfig) { + const CartesianAxis: ICartesianAxis = Factory.getComponent('axis'); const axis = new CartesianAxis(axisConfig, cellGroup.attribute.width, cellGroup.attribute.height, padding, table); cellGroup.clear(); cellGroup.appendChild(axis.component); diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts index 6e7c77ca5..8069fe478 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts @@ -1,8 +1,6 @@ -import type { ILine, ISymbol, IThemeSpec } from '@src/vrender'; -import { createLine, createSymbol } from '@src/vrender'; -import { PointScale, LinearScale } from '@visactor/vscale'; +import type { IThemeSpec } from '@src/vrender'; import { Group } from '../../graphic/group'; -import type { CellInfo, CheckboxColumnDefine, CheckboxStyleOption, SparklineSpec } from '../../../ts-types'; +import type { CheckboxColumnDefine, CheckboxStyleOption } from '../../../ts-types'; import type { BaseTableAPI } from '../../../ts-types/base-table'; import { isObject } from '@visactor/vutils'; import type { CheckboxAttributes } from '@visactor/vrender-components'; diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts index a384b98ab..fea59a25a 100644 --- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts +++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts @@ -14,8 +14,8 @@ import { getQuadProps } from '../utils/padding'; import { getProp } from '../utils/get-prop'; import type { BaseTableAPI, HeaderData } from '../../ts-types/base-table'; import type { PivotHeaderLayoutMap } from '../../layout/pivot-header-layout'; -import { getAxisConfigInPivotChart } from '../../layout/chart-helper/get-axis-config'; -import { computeAxisComponentWidth } from '../../components/axis/get-axis-component-size'; +import type { ComputeAxisComponentWidth } from '../../components/axis/get-axis-component-size'; +import { Factory } from '../../core/factory'; import { Group as VGroup } from '@src/vrender'; import { isArray, isFunction, isNumber, isObject, isValid } from '@visactor/vutils'; import { decodeReactDom, dealPercentCalc } from '../component/custom'; @@ -301,8 +301,9 @@ function computeAutoColWidth( // 判断透视图轴组件 if (table.isPivotChart()) { const layout = table.internalProps.layoutMap as PivotHeaderLayoutMap; - const axisConfig = getAxisConfigInPivotChart(col, row, layout); + const axisConfig = layout.getAxisConfigInPivotChart(col, row); if (axisConfig) { + const computeAxisComponentWidth: ComputeAxisComponentWidth = Factory.getFunction('computeAxisComponentWidth'); const axisWidth = computeAxisComponentWidth(axisConfig, table); if (typeof axisWidth === 'number') { maxWidth = Math.max(axisWidth, maxWidth); diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts index 6ddd91d55..8ae8a201f 100644 --- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -8,8 +8,8 @@ import type { ColumnData, ColumnDefine, TextColumnDefine } from '../../ts-types/ import { getProp } from '../utils/get-prop'; import { getQuadProps } from '../utils/padding'; import { dealWithRichTextIcon } from '../utils/text-icon-layout'; -import { getAxisConfigInPivotChart } from '../../layout/chart-helper/get-axis-config'; -import { computeAxisComponentHeight } from '../../components/axis/get-axis-component-size'; +import type { ComputeAxisComponentHeight } from '../../components/axis/get-axis-component-size'; +import { Factory } from '../../core/factory'; import { isArray, isFunction, isNumber, isObject, isValid } from '@visactor/vutils'; import { CheckBox } from '@visactor/vrender-components'; import { decodeReactDom, dealPercentCalc } from '../component/custom'; @@ -351,8 +351,10 @@ export function computeRowHeight(row: number, startCol: number, endCol: number, // Axis component height calculation if (table.isPivotChart()) { const layout = table.internalProps.layoutMap as PivotHeaderLayoutMap; - const axisConfig = getAxisConfigInPivotChart(col, row, layout); + const axisConfig = layout.getAxisConfigInPivotChart(col, row); if (axisConfig) { + const computeAxisComponentHeight: ComputeAxisComponentHeight = + Factory.getFunction('computeAxisComponentHeight'); const axisWidth = computeAxisComponentHeight(axisConfig, table); if (typeof axisWidth === 'number') { maxHeight = isValid(maxHeight) ? Math.max(axisWidth, maxHeight) : axisWidth; diff --git a/packages/vtable/src/scenegraph/layout/update-width.ts b/packages/vtable/src/scenegraph/layout/update-width.ts index cb8199392..bc55e24e3 100644 --- a/packages/vtable/src/scenegraph/layout/update-width.ts +++ b/packages/vtable/src/scenegraph/layout/update-width.ts @@ -1,6 +1,7 @@ import type { IGraphic } from '@src/vrender'; import type { ProgressBarStyle } from '../../body-helper/style/ProgressBarStyle'; -import { CartesianAxis } from '../../components/axis/axis'; +import type { ICartesianAxis } from '../../components/axis/axis'; +import { Factory } from '../../core/factory'; import { getStyleTheme } from '../../core/tableHelper'; import type { BaseTableAPI, HeaderData } from '../../ts-types/base-table'; import type { IProgressbarColumnBodyDefine } from '../../ts-types/list-table/define/progressbar-define'; @@ -327,6 +328,7 @@ function updateCellWidth( const cellStyle = scene.table._getCellStyle(col, row); const padding = getQuadProps(getProp('padding', cellStyle, col, row, scene.table)); if (axisConfig) { + const CartesianAxis: ICartesianAxis = Factory.getComponent('axis'); const axis = new CartesianAxis( axisConfig, cellGroup.attribute.width, diff --git a/packages/vtable/src/scenegraph/refresh-node/update-chart.ts b/packages/vtable/src/scenegraph/refresh-node/update-chart.ts index 3f4b06281..d302c3780 100644 --- a/packages/vtable/src/scenegraph/refresh-node/update-chart.ts +++ b/packages/vtable/src/scenegraph/refresh-node/update-chart.ts @@ -1,6 +1,7 @@ import { isEqual } from '@visactor/vutils'; import type { PivotChart } from '../../PivotChart'; -import { CartesianAxis } from '../../components/axis/axis'; +import type { ICartesianAxis } from '../../components/axis/axis'; +import { Factory } from '../../core/factory'; import type { BaseTableAPI } from '../../ts-types/base-table'; import type { Chart } from '../graphic/chart'; import type { Group } from '../graphic/group'; @@ -209,6 +210,7 @@ function updateTableAxes(containerGroup: Group, table: BaseTableAPI) { const axisConfig = table.internalProps.layoutMap.getAxisConfigInPivotChart(cell.col, cell.row); const cellStyle = table._getCellStyle(cell.col, cell.row); const padding = getQuadProps(getProp('padding', cellStyle, cell.col, cell.row, table)); + const CartesianAxis: ICartesianAxis = Factory.getComponent('axis'); const axis = new CartesianAxis(axisConfig, cell.attribute.width, cell.attribute.height, padding, table); cell.clear(); cell.appendChild(axis.component); diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 600ba0203..767952da2 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -560,7 +560,7 @@ export interface BaseTableAPI { ) => EventListenerId; // &((type: string, listener: AnyListener) => EventListenerId); - _vDataSet: DataSet; + _vDataSet?: DataSet; /** 场景树对象 */ scenegraph: Scenegraph; /** 状态管理模块 */ From caf4a23d0350d6bca38098617d97d58d7c7ecf54 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Wed, 26 Jun 2024 19:59:42 +0800 Subject: [PATCH 04/14] feat: move the rest of components to factory --- packages/vtable/src/ListTable.ts | 25 ++++++++++++++++-- packages/vtable/src/PivotChart.ts | 25 +++++++++++++++--- packages/vtable/src/PivotTable.ts | 26 +++++++++++++++++-- packages/vtable/src/components/axis/axis.ts | 8 +++--- .../src/components/empty-tip/empty-tip.ts | 4 +++ packages/vtable/src/components/index.ts | 25 ++++++++++++++++++ .../src/components/legend/create-legend.ts | 4 +++ .../src/components/menu/dom/MenuHandler.ts | 3 +++ packages/vtable/src/components/title/title.ts | 4 +++ .../src/components/tooltip/TooltipHandler.ts | 3 +++ packages/vtable/src/core/BaseTable.ts | 13 +++++++--- 11 files changed, 126 insertions(+), 14 deletions(-) diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 847857947..49955575b 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -23,7 +23,7 @@ import { _setDataSource, _setRecords, sortRecords } from './core/tableHelper'; import { BaseTable } from './core'; 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 type { ITitleComponent } from './components/title/title'; import { Env } from './tools/env'; import * as editors from './edit/editors'; import { EditManeger } from './edit/edit-manager'; @@ -35,7 +35,23 @@ import type { ColumnData, ColumnDefine } from './ts-types/list-table/layout-map/ import { getCellRadioState, setCellRadioState } from './state/radio/radio'; import { cloneDeepSpec } from '@visactor/vutils-extension'; import { setCellCheckboxState } from './state/checkbox/checkbox'; -import { EmptyTip } from './components/empty-tip/empty-tip'; +import type { IEmptyTipComponent } from './components/empty-tip/empty-tip'; +import { Factory } from './core/factory'; +import { + registerAxis, + registerEmptyTip, + registerLegend, + registerMenu, + registerTitle, + registerTooltip +} from './components'; + +registerAxis(); +registerEmptyTip(); +registerLegend(); +registerMenu(); +registerTitle(); +registerTooltip(); export class ListTable extends BaseTable implements ListTableAPI { declare internalProps: ListTableProtected; @@ -96,6 +112,7 @@ export class ListTable extends BaseTable implements ListTableAPI { this.setRecords([]); } if (options.title) { + const Title = Factory.getComponent('title') as ITitleComponent; internalProps.title = new Title(options.title, this); this.scenegraph.resize(); } @@ -103,6 +120,7 @@ export class ListTable extends BaseTable implements ListTableAPI { if (this.internalProps.emptyTip) { this.internalProps.emptyTip.resetVisible(); } else { + const EmptyTip = Factory.getComponent('emptyTip') as IEmptyTipComponent; this.internalProps.emptyTip = new EmptyTip(this.options.emptyTip, this); this.internalProps.emptyTip.resetVisible(); } @@ -434,6 +452,7 @@ export class ListTable extends BaseTable implements ListTableAPI { this.render(); } if (options.title) { + const Title = Factory.getComponent('title') as ITitleComponent; internalProps.title = new Title(options.title, this); this.scenegraph.resize(); } @@ -441,6 +460,7 @@ export class ListTable extends BaseTable implements ListTableAPI { if (this.internalProps.emptyTip) { this.internalProps.emptyTip.resetVisible(); } else { + const EmptyTip = Factory.getComponent('emptyTip') as IEmptyTipComponent; this.internalProps.emptyTip = new EmptyTip(this.options.emptyTip, this); this.internalProps.emptyTip.resetVisible(); } @@ -1033,6 +1053,7 @@ export class ListTable extends BaseTable implements ListTableAPI { if (this.internalProps.emptyTip) { this.internalProps.emptyTip.resetVisible(); } else { + const EmptyTip = Factory.getComponent('emptyTip') as IEmptyTipComponent; this.internalProps.emptyTip = new EmptyTip(this.options.emptyTip, this); this.internalProps.emptyTip.resetVisible(); } diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index 28890b44e..8f01c6dc0 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -38,7 +38,7 @@ import { clearChartCacheImage, updateChartData } from './scenegraph/refresh-node import type { ITableAxisOption } from './ts-types/component/axis'; import { cloneDeep, isArray } from '@visactor/vutils'; import type { DiscreteLegend } from '@visactor/vrender-components'; -import { Title } from './components/title/title'; +import type { ITitleComponent } from './components/title/title'; import { Env } from './tools/env'; import { TABLE_EVENT_TYPE } from './core/TABLE_EVENT_TYPE'; import type { IndicatorData } from './ts-types/list-table/layout-map/api'; @@ -48,10 +48,23 @@ import { DimensionTree, type LayouTreeNode } from './layout/tree-helper'; import { IndicatorDimensionKeyPlaceholder } from './tools/global'; import { checkHasCartesianChart } from './layout/chart-helper/get-chart-spec'; import { supplementIndicatorNodesForCustomTree } from './layout/layout-helper'; -import { EmptyTip } from './components/empty-tip/empty-tip'; -import { registerAxis } from './components'; +import type { IEmptyTipComponent } from './components/empty-tip/empty-tip'; +import { Factory } from './core/factory'; +import { + registerAxis, + registerEmptyTip, + registerLegend, + registerMenu, + registerTitle, + registerTooltip +} from './components'; -// registerAxis(); +registerAxis(); +registerEmptyTip(); +registerLegend(); +registerMenu(); +registerTitle(); +registerTooltip(); export class PivotChart extends BaseTable implements PivotChartAPI { layoutNodeId: { seqId: number } = { seqId: 0 }; declare internalProps: PivotChartProtected; @@ -231,6 +244,7 @@ export class PivotChart extends BaseTable implements PivotChartAPI { // 生成单元格场景树 this.scenegraph.createSceneGraph(); if (options.title) { + const Title = Factory.getComponent('title') as ITitleComponent; this.internalProps.title = new Title(options.title, this); this.scenegraph.resize(); } @@ -238,6 +252,7 @@ export class PivotChart extends BaseTable implements PivotChartAPI { if (this.internalProps.emptyTip) { this.internalProps.emptyTip.resetVisible(); } else { + const EmptyTip = Factory.getComponent('emptyTip') as IEmptyTipComponent; this.internalProps.emptyTip = new EmptyTip(this.options.emptyTip, this); this.internalProps.emptyTip.resetVisible(); } @@ -463,6 +478,7 @@ export class PivotChart extends BaseTable implements PivotChartAPI { // 生成单元格场景树 this.scenegraph.createSceneGraph(); if (options.title) { + const Title = Factory.getComponent('title') as ITitleComponent; this.internalProps.title = new Title(options.title, this); this.scenegraph.resize(); } @@ -470,6 +486,7 @@ export class PivotChart extends BaseTable implements PivotChartAPI { if (this.internalProps.emptyTip) { this.internalProps.emptyTip.resetVisible(); } else { + const EmptyTip = Factory.getComponent('emptyTip') as IEmptyTipComponent; this.internalProps.emptyTip = new EmptyTip(this.options.emptyTip, this); this.internalProps.emptyTip.resetVisible(); } diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 9c23efa46..e32705935 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -28,7 +28,7 @@ import { cellInRange, emptyFn } from './tools/helper'; import { Dataset } from './dataset/dataset'; import { BaseTable } from './core/BaseTable'; import type { BaseTableAPI, HeaderData, PivotTableProtected } from './ts-types/base-table'; -import { Title } from './components/title/title'; +import type { ITitleComponent } from './components/title/title'; import { cloneDeep, isNumber, isValid } from '@visactor/vutils'; import { Env } from './tools/env'; import type { ITreeLayoutHeadNode } from './layout/tree-helper'; @@ -43,7 +43,24 @@ import { isAllDigits } from './tools/util'; import type { IndicatorData } from './ts-types/list-table/layout-map/api'; import { cloneDeepSpec } from '@visactor/vutils-extension'; import { parseColKeyRowKeyForPivotTable, supplementIndicatorNodesForCustomTree } from './layout/layout-helper'; -import { EmptyTip } from './components/empty-tip/empty-tip'; +import type { IEmptyTipComponent } from './components/empty-tip/empty-tip'; +import { Factory } from './core/factory'; +import { + registerAxis, + registerEmptyTip, + registerLegend, + registerMenu, + registerTitle, + registerTooltip +} from './components'; + +registerAxis(); +registerEmptyTip(); +registerLegend(); +registerMenu(); +registerTitle(); +registerTooltip(); + export class PivotTable extends BaseTable implements PivotTableAPI { layoutNodeId: { seqId: number } = { seqId: 0 }; declare internalProps: PivotTableProtected; @@ -207,6 +224,7 @@ export class PivotTable extends BaseTable implements PivotTableAPI { // this.render(); if (options.title) { + const Title = Factory.getComponent('title') as ITitleComponent; this.internalProps.title = new Title(options.title, this); this.scenegraph.resize(); } @@ -214,6 +232,7 @@ export class PivotTable extends BaseTable implements PivotTableAPI { if (this.internalProps.emptyTip) { this.internalProps.emptyTip.resetVisible(); } else { + const EmptyTip = Factory.getComponent('emptyTip') as IEmptyTipComponent; this.internalProps.emptyTip = new EmptyTip(this.options.emptyTip, this); this.internalProps.emptyTip.resetVisible(); } @@ -417,6 +436,7 @@ export class PivotTable extends BaseTable implements PivotTableAPI { // this.scenegraph.resize(); // } if (options.title) { + const Title = Factory.getComponent('title') as ITitleComponent; this.internalProps.title = new Title(options.title, this); this.scenegraph.resize(); } @@ -424,6 +444,7 @@ export class PivotTable extends BaseTable implements PivotTableAPI { if (this.internalProps.emptyTip) { this.internalProps.emptyTip.resetVisible(); } else { + const EmptyTip = Factory.getComponent('emptyTip') as IEmptyTipComponent; this.internalProps.emptyTip = new EmptyTip(this.options.emptyTip, this); this.internalProps.emptyTip.resetVisible(); } @@ -1519,6 +1540,7 @@ export class PivotTable extends BaseTable implements PivotTableAPI { if (this.internalProps.emptyTip) { this.internalProps.emptyTip.resetVisible(); } else { + const EmptyTip = Factory.getComponent('emptyTip') as IEmptyTipComponent; this.internalProps.emptyTip = new EmptyTip(this.options.emptyTip, this); this.internalProps.emptyTip.resetVisible(); } diff --git a/packages/vtable/src/components/axis/axis.ts b/packages/vtable/src/components/axis/axis.ts index a796fc6d4..2cd5bd4ca 100644 --- a/packages/vtable/src/components/axis/axis.ts +++ b/packages/vtable/src/components/axis/axis.ts @@ -123,14 +123,16 @@ export class CartesianAxis { } initData() { + if (!this.table._vDataSet) { + this.table._vDataSet = new DataSet(); + } + registerDataSetInstanceParser(this.table._vDataSet, 'scale', scaleParser); registerDataSetInstanceTransform(this.table._vDataSet, 'ticks', ticks); const label = this.option.label || {}; const tick = this.option.tick || {}; - if (!this.table._vDataSet) { - this.table._vDataSet = new DataSet(); - } + const tickData = new DataView(this.table._vDataSet) .parse(this.scale._scale, { type: 'scale' diff --git a/packages/vtable/src/components/empty-tip/empty-tip.ts b/packages/vtable/src/components/empty-tip/empty-tip.ts index 971b61d8a..f5a0a55c6 100644 --- a/packages/vtable/src/components/empty-tip/empty-tip.ts +++ b/packages/vtable/src/components/empty-tip/empty-tip.ts @@ -9,6 +9,10 @@ import type { PivotTable } from '../../PivotTable'; const emptyTipSvg = ''; +export interface IEmptyTipComponent { + new (emptyTipOption: IEmptyTip | true, table: BaseTableAPI): EmptyTip; +} + export class EmptyTip { table: BaseTableAPI; _emptyTipOption: IEmptyTip = { diff --git a/packages/vtable/src/components/index.ts b/packages/vtable/src/components/index.ts index d9b0c0f39..546696748 100644 --- a/packages/vtable/src/components/index.ts +++ b/packages/vtable/src/components/index.ts @@ -2,6 +2,11 @@ import { Factory } from '../core/factory'; import { getAxisConfigInPivotChart } from '../layout/chart-helper/get-axis-config'; import { CartesianAxis } from './axis/axis'; import { computeAxisComponentHeight, computeAxisComponentWidth } from './axis/get-axis-component-size'; +import { EmptyTip } from './empty-tip/empty-tip'; +import { createLegend } from './legend/create-legend'; +import { MenuHandler } from './menu/dom/MenuHandler'; +import { Title } from './title/title'; +import { TooltipHandler } from './tooltip/TooltipHandler'; export const registerAxis = () => { Factory.registerComponent('axis', CartesianAxis); @@ -9,3 +14,23 @@ export const registerAxis = () => { Factory.registerFunction('computeAxisComponentHeight', computeAxisComponentHeight); Factory.registerFunction('getAxisConfigInPivotChart', getAxisConfigInPivotChart); }; + +export const registerEmptyTip = () => { + Factory.registerComponent('emptyTip', EmptyTip); +}; + +export const registerLegend = () => { + Factory.registerFunction('createLegend', createLegend); +}; + +export const registerMenu = () => { + Factory.registerComponent('menuHandler', MenuHandler); +}; + +export const registerTitle = () => { + Factory.registerComponent('title', Title); +}; + +export const registerTooltip = () => { + Factory.registerComponent('tooltipHandler', TooltipHandler); +}; diff --git a/packages/vtable/src/components/legend/create-legend.ts b/packages/vtable/src/components/legend/create-legend.ts index 25bc710c6..4665f3aec 100644 --- a/packages/vtable/src/components/legend/create-legend.ts +++ b/packages/vtable/src/components/legend/create-legend.ts @@ -3,6 +3,10 @@ import { DiscreteTableLegend } from './discrete-legend/discrete-legend'; import type { BaseTableAPI } from '../../ts-types/base-table'; import { ContinueTableLegend } from './continue-legend/continue-legend'; +export type CreateLegend = ( + option: ITableLegendOption, + table: BaseTableAPI +) => DiscreteTableLegend | ContinueTableLegend; export function createLegend(option: ITableLegendOption, table: BaseTableAPI) { if (option.type === 'color' || option.type === 'size') { return new ContinueTableLegend(option, table); diff --git a/packages/vtable/src/components/menu/dom/MenuHandler.ts b/packages/vtable/src/components/menu/dom/MenuHandler.ts index 70dafeb62..5e4cbb349 100644 --- a/packages/vtable/src/components/menu/dom/MenuHandler.ts +++ b/packages/vtable/src/components/menu/dom/MenuHandler.ts @@ -115,6 +115,9 @@ type AttachInfo = { range: CellRange; }; +export interface IMenuHandler { + new (table: BaseTableAPI): MenuHandler; +} export class MenuHandler { private _table: BaseTableAPI; private _menuInstances?: { [type: string]: BaseMenu }; diff --git a/packages/vtable/src/components/title/title.ts b/packages/vtable/src/components/title/title.ts index a0fcd07c7..1930a72ea 100644 --- a/packages/vtable/src/components/title/title.ts +++ b/packages/vtable/src/components/title/title.ts @@ -5,6 +5,10 @@ import type { ITitle } from '../../ts-types/component/title'; import { getQuadProps } from '../../scenegraph/utils/padding'; import type { BaseTableAPI } from '../../ts-types/base-table'; import { isEqual } from '@visactor/vutils'; + +export interface ITitleComponent { + new (titleOption: ITitle, table: BaseTableAPI): Title; +} export class Title { table: BaseTableAPI; _titleOption: ITitle; diff --git a/packages/vtable/src/components/tooltip/TooltipHandler.ts b/packages/vtable/src/components/tooltip/TooltipHandler.ts index 89afbf690..6ba0568ca 100644 --- a/packages/vtable/src/components/tooltip/TooltipHandler.ts +++ b/packages/vtable/src/components/tooltip/TooltipHandler.ts @@ -21,6 +21,9 @@ type AttachInfo = { range: CellRange; tooltipOptions: TooltipOptions; }; +export interface ITooltipHandler { + new (table: BaseTableAPI, confine: boolean): TooltipHandler; +} export class TooltipHandler { private _table: BaseTableAPI; diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index ccb26ba9d..7f5d485b8 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -70,7 +70,7 @@ import { EventManager } from '../event/event'; import { BodyHelper } from '../body-helper/body-helper'; import { HeaderHelper } from '../header-helper/header-helper'; import type { PivotHeaderLayoutMap } from '../layout/pivot-header-layout'; -import { TooltipHandler } from '../components/tooltip/TooltipHandler'; +import type { ITooltipHandler } from '../components/tooltip/TooltipHandler'; import type { CachedDataSource, DataSource } from '../data'; import { AABBBounds, @@ -106,7 +106,7 @@ import { getStyleTheme, updateRootElementPadding } from './tableHelper'; -import { MenuHandler } from '../components/menu/dom/MenuHandler'; +import type { IMenuHandler } from '../components/menu/dom/MenuHandler'; import type { BaseTableAPI, BaseTableConstructorOptions, @@ -115,7 +115,7 @@ import type { } from '../ts-types/base-table'; import { FocusInput } from './FouseInput'; import { defaultPixelRatio } from '../tools/pixel-ratio'; -import { createLegend } from '../components/legend/create-legend'; +import type { CreateLegend } from '../components/legend/create-legend'; import type { DataSet } from '@visactor/vdataset'; import { Title } from '../components/title/title'; import type { Chart } from '../scenegraph/graphic/chart'; @@ -131,6 +131,7 @@ import type { ITextGraphicAttribute } from '@src/vrender'; import { ReactCustomLayout } from '../components/react/react-custom-layout'; import type { ISortedMapItem } from '../data/DataSource'; import { hasAutoImageColumn } from '../layout/layout-helper'; +import { Factory } from './factory'; const { toBoxArray } = utilStyle; const { isTouchEvent } = event; @@ -412,6 +413,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { if (options.legends) { internalProps.legends = []; + const createLegend = Factory.getFunction('createLegend') as CreateLegend; if (Array.isArray(options.legends)) { for (let i = 0; i < options.legends.length; i++) { internalProps.legends.push(createLegend(options.legends[i], this)); @@ -439,6 +441,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { options.tooltip ); if (internalProps.tooltip.renderMode === 'html') { + const TooltipHandler = Factory.getComponent('tooltipHandler') as ITooltipHandler; internalProps.tooltipHandler = new TooltipHandler(this, internalProps.tooltip.confine); } internalProps.menu = Object.assign( @@ -455,6 +458,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { (this.globalDropDownMenu = options.menu.defaultHeaderMenuItems); if (internalProps.menu.renderMode === 'html') { + const MenuHandler = Factory.getComponent('menuHandler') as IMenuHandler; internalProps.menuHandler = new MenuHandler(this); } @@ -2302,6 +2306,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.eventManager.updateEventBinder(); if (options.legends) { internalProps.legends = []; + const createLegend = Factory.getFunction('createLegend') as CreateLegend; if (Array.isArray(options.legends)) { for (let i = 0; i < options.legends.length; i++) { internalProps.legends.push(createLegend(options.legends[i], this)); @@ -2334,6 +2339,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { options.tooltip ); if (internalProps.tooltip.renderMode === 'html' && !internalProps.tooltipHandler) { + const TooltipHandler = Factory.getComponent('tooltipHandler') as ITooltipHandler; internalProps.tooltipHandler = new TooltipHandler(this, internalProps.tooltip.confine); } @@ -2352,6 +2358,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { (this.globalDropDownMenu = options.menu.defaultHeaderMenuItems); if (internalProps.menu.renderMode === 'html' && !internalProps.menuHandler) { + const MenuHandler = Factory.getComponent('menuHandler') as IMenuHandler; internalProps.menuHandler = new MenuHandler(this); } this.clearCellStyleCache(); From 3fe5e47c10e9bb8fd49001a5c553ebe47da3a36b Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Wed, 26 Jun 2024 21:38:32 +0800 Subject: [PATCH 05/14] feat: move cell type into factory --- packages/vtable/src/ListTable.ts | 15 ------- packages/vtable/src/PivotChart.ts | 20 +++++++++ packages/vtable/src/PivotTable.ts | 19 ++++++++ packages/vtable/src/index.ts | 5 +++ .../layout/chart-helper/get-axis-config.ts | 4 +- .../layout/chart-helper/get-axis-domain.ts | 2 + .../src/layout/chart-helper/get-chart-spec.ts | 6 ++- .../scenegraph/group-creater/cell-helper.ts | 29 ++++++++----- .../group-creater/cell-type/chart-cell.ts | 2 + .../group-creater/cell-type/checkbox-cell.ts | 2 + .../group-creater/cell-type/image-cell.ts | 2 + .../group-creater/cell-type/index.ts | 43 +++++++++++++++++++ .../cell-type/progress-bar-cell.ts | 2 + .../group-creater/cell-type/radio-cell.ts | 2 + .../cell-type/spark-line-cell.ts | 2 + .../group-creater/cell-type/text-cell.ts | 2 + .../group-creater/cell-type/video-cell.ts | 2 + .../src/scenegraph/layout/update-height.ts | 7 ++- .../src/scenegraph/layout/update-width.ts | 6 ++- packages/vtable/src/scenegraph/scenegraph.ts | 2 +- packages/vtable/src/vrender.ts | 3 ++ 21 files changed, 145 insertions(+), 32 deletions(-) create mode 100644 packages/vtable/src/scenegraph/group-creater/cell-type/index.ts diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 49955575b..01cbafab8 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -37,21 +37,6 @@ import { cloneDeepSpec } from '@visactor/vutils-extension'; import { setCellCheckboxState } from './state/checkbox/checkbox'; import type { IEmptyTipComponent } from './components/empty-tip/empty-tip'; import { Factory } from './core/factory'; -import { - registerAxis, - registerEmptyTip, - registerLegend, - registerMenu, - registerTitle, - registerTooltip -} from './components'; - -registerAxis(); -registerEmptyTip(); -registerLegend(); -registerMenu(); -registerTitle(); -registerTooltip(); export class ListTable extends BaseTable implements ListTableAPI { declare internalProps: ListTableProtected; diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index 8f01c6dc0..b6b97beb5 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -58,6 +58,16 @@ import { registerTitle, registerTooltip } from './components'; +import { + registerChartCell, + registerCheckboxCell, + registerImageCell, + registerProgressBarCell, + registerRadioCell, + registerSparkLineCell, + registerTextCell, + registerVideoCell +} from './scenegraph/group-creater/cell-type'; registerAxis(); registerEmptyTip(); @@ -65,6 +75,16 @@ registerLegend(); registerMenu(); registerTitle(); registerTooltip(); + +registerChartCell(); +registerCheckboxCell(); +registerImageCell(); +registerProgressBarCell(); +registerRadioCell(); +registerSparkLineCell(); +registerTextCell(); +registerVideoCell(); + export class PivotChart extends BaseTable implements PivotChartAPI { layoutNodeId: { seqId: number } = { seqId: 0 }; declare internalProps: PivotChartProtected; diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index e32705935..2c750ee22 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -53,6 +53,16 @@ import { registerTitle, registerTooltip } from './components'; +import { + registerChartCell, + registerCheckboxCell, + registerImageCell, + registerProgressBarCell, + registerRadioCell, + registerSparkLineCell, + registerTextCell, + registerVideoCell +} from './scenegraph/group-creater/cell-type'; registerAxis(); registerEmptyTip(); @@ -61,6 +71,15 @@ registerMenu(); registerTitle(); registerTooltip(); +registerChartCell(); +registerCheckboxCell(); +registerImageCell(); +registerProgressBarCell(); +registerRadioCell(); +registerSparkLineCell(); +registerTextCell(); +registerVideoCell(); + export class PivotTable extends BaseTable implements PivotTableAPI { layoutNodeId: { seqId: number } = { seqId: 0 }; declare internalProps: PivotTableProtected; diff --git a/packages/vtable/src/index.ts b/packages/vtable/src/index.ts index 9841836a2..7b75b93eb 100644 --- a/packages/vtable/src/index.ts +++ b/packages/vtable/src/index.ts @@ -31,6 +31,7 @@ import type { TextBaselineType } from './ts-types'; import { ListTable } from './ListTable'; +import { ListTableSimple } from './ListTable-simple'; import { PivotTable } from './PivotTable'; import { PivotChart } from './PivotChart'; import type { MousePointerCellEvent } from './ts-types/events'; @@ -61,6 +62,7 @@ export { TYPES, core, ListTable, + ListTableSimple, ListTableConstructorOptions, PivotTable, PivotTableConstructorOptions, @@ -114,3 +116,6 @@ function clearGlobal() { // columns.type.clearGlobal(); } TYPES.AggregationType; + +export * from './components'; +export * from './scenegraph/group-creater/cell-type'; diff --git a/packages/vtable/src/layout/chart-helper/get-axis-config.ts b/packages/vtable/src/layout/chart-helper/get-axis-config.ts index d812ad46c..d3d5439c4 100644 --- a/packages/vtable/src/layout/chart-helper/get-axis-config.ts +++ b/packages/vtable/src/layout/chart-helper/get-axis-config.ts @@ -2,9 +2,10 @@ import { isArray, isNumber, isValid, merge } from '@visactor/vutils'; import type { PivotHeaderLayoutMap } from '../pivot-header-layout'; import type { ITableAxisOption } from '../../ts-types/component/axis'; import type { PivotChart } from '../../PivotChart'; -import { getAxisDomainRangeAndLabels } from './get-axis-domain'; import type { CollectedValue } from '../../ts-types'; import { getNewRangeToAlign } from './zero-align'; +import { Factory } from '../../core/factory'; +import type { GetAxisDomainRangeAndLabels } from './get-axis-domain'; export type GetAxisConfigInPivotChart = (col: number, row: number, layout: PivotHeaderLayoutMap) => any; export function getAxisConfigInPivotChart(col: number, row: number, layout: PivotHeaderLayoutMap): any { @@ -559,6 +560,7 @@ function getRange( range.min = range.min < 0 ? -1 : 0; range.max = range.max > 0 ? 1 : 0; } + const getAxisDomainRangeAndLabels = Factory.getFunction('getAxisDomainRangeAndLabels') as GetAxisDomainRangeAndLabels; const { range: niceRange, ticks } = getAxisDomainRangeAndLabels( range.min, range.max, diff --git a/packages/vtable/src/layout/chart-helper/get-axis-domain.ts b/packages/vtable/src/layout/chart-helper/get-axis-domain.ts index 0dbe96002..2e5e6cf23 100644 --- a/packages/vtable/src/layout/chart-helper/get-axis-domain.ts +++ b/packages/vtable/src/layout/chart-helper/get-axis-domain.ts @@ -89,3 +89,5 @@ export function getAxisDomainRangeAndLabels( ticks: scaleTicks }; } + +export type GetAxisDomainRangeAndLabels = typeof getAxisDomainRangeAndLabels; 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 18bbf1362..5f49e97df 100644 --- a/packages/vtable/src/layout/chart-helper/get-chart-spec.ts +++ b/packages/vtable/src/layout/chart-helper/get-chart-spec.ts @@ -2,10 +2,11 @@ import { cloneDeep, isArray, isNumber, merge } from '@visactor/vutils'; import type { PivotHeaderLayoutMap } from '../pivot-header-layout'; import type { SimpleHeaderLayoutMap } from '../simple-header-layout'; import { getAxisOption, getAxisRange } from './get-axis-config'; -import { getAxisDomainRangeAndLabels } from './get-axis-domain'; import { getNewRangeToAlign } from './zero-align'; import type { IChartIndicator, IIndicator } from '../../ts-types'; import { cloneDeepSpec } from '@visactor/vutils-extension'; +import { Factory } from '../../core/factory'; +import type { GetAxisDomainRangeAndLabels } from './get-axis-domain'; const NO_AXISID_FRO_VTABLE = 'NO_AXISID_FRO_VTABLE'; @@ -424,6 +425,9 @@ function getRange( range.max = Math.max(range.max, 0); } if (axisOption?.nice) { + const getAxisDomainRangeAndLabels = Factory.getFunction( + 'getAxisDomainRangeAndLabels' + ) as GetAxisDomainRangeAndLabels; const { range: axisRange } = getAxisDomainRangeAndLabels( range.min, range.max, diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index d58713507..025fda7db 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -16,25 +16,25 @@ import type { import { dealWithCustom } from '../component/custom'; import type { Group } from '../graphic/group'; import { getProp } from '../utils/get-prop'; -import { createChartCellGroup } from './cell-type/chart-cell'; -import { createImageCellGroup } from './cell-type/image-cell'; -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 { CreateChartCellGroup } from './cell-type/chart-cell'; +import type { CreateImageCellGroup } from './cell-type/image-cell'; +import type { CreateProgressBarCell } from './cell-type/progress-bar-cell'; +import type { CreateSparkLineCellGroup } from './cell-type/spark-line-cell'; +import type { CreateTextCellGroup } from './cell-type/text-cell'; +import type { CreateVideoCellGroup } from './cell-type/video-cell'; import type { BaseTableAPI, HeaderData } from '../../ts-types/base-table'; import { getCellCornerRadius, getStyleTheme } from '../../core/tableHelper'; import { isPromise } from '../../tools/helper'; import { dealPromiseData } from '../utils/deal-promise-data'; import type { ICartesianAxis } from '../../components/axis/axis'; import { Factory } from '../../core/factory'; -import { createCheckboxCellGroup } from './cell-type/checkbox-cell'; +import type { CreateCheckboxCellGroup } from './cell-type/checkbox-cell'; import { getHierarchyOffset } from '../utils/get-hierarchy-offset'; import { getQuadProps } from '../utils/padding'; import { updateCellContentHeight, updateCellContentWidth } from '../utils/text-icon-layout'; import { isArray } from '@visactor/vutils'; import { breakString } from '../utils/break-string'; -import { createRadioCellGroup } from './cell-type/radio-cell'; +import type { CreateRadioCellGroup } from './cell-type/radio-cell'; export function createCell( type: ColumnTypeOption, @@ -161,7 +161,8 @@ export function createCell( } } - cellGroup = createCellGroup( + const createTextCellGroup = Factory.getFunction('createTextCellGroup') as CreateTextCellGroup; + cellGroup = createTextCellGroup( table, value, columnGroup, @@ -201,6 +202,7 @@ export function createCell( } } else if (type === 'image') { // 创建图片单元格 + const createImageCellGroup = Factory.getFunction('createImageCellGroup') as CreateImageCellGroup; cellGroup = createImageCellGroup( columnGroup, 0, @@ -220,6 +222,7 @@ export function createCell( ); } else if (type === 'video') { // 创建视频单元格 + const createVideoCellGroup = Factory.getFunction('createVideoCellGroup') as CreateVideoCellGroup; cellGroup = createVideoCellGroup( columnGroup, 0, @@ -239,6 +242,7 @@ export function createCell( ); } else if (type === 'chart') { const chartInstance = table.internalProps.layoutMap.getChartInstance(col, row); + const createChartCellGroup = Factory.getFunction('createChartCellGroup') as CreateChartCellGroup; cellGroup = createChartCellGroup( null, columnGroup, @@ -263,7 +267,8 @@ export function createCell( const style = table._getCellStyle(col, row) as ProgressBarStyle; const dataValue = table.getCellOriginValue(col, row); // 创建基础文字单元格 - cellGroup = createCellGroup( + const createTextCellGroup = Factory.getFunction('createTextCellGroup') as CreateTextCellGroup; + cellGroup = createTextCellGroup( table, value, columnGroup, @@ -286,6 +291,7 @@ export function createCell( ); // 创建bar group + const createProgressBarCell = Factory.getFunction('createProgressBarCell') as CreateProgressBarCell; const progressBarGroup = createProgressBarCell( define as ProgressbarColumnDefine, style, @@ -304,6 +310,7 @@ export function createCell( cellGroup.appendChild(progressBarGroup); } } else if (type === 'sparkline') { + const createSparkLineCellGroup = Factory.getFunction('createSparkLineCellGroup') as CreateSparkLineCellGroup; cellGroup = createSparkLineCellGroup( null, columnGroup, @@ -319,6 +326,7 @@ export function createCell( isAsync ); } else if (type === 'checkbox') { + const createCheckboxCellGroup = Factory.getFunction('createCheckboxCellGroup') as CreateCheckboxCellGroup; cellGroup = createCheckboxCellGroup( null, columnGroup, @@ -338,6 +346,7 @@ export function createCell( isAsync ); } else if (type === 'radio') { + const createRadioCellGroup = Factory.getFunction('createRadioCellGroup') as CreateRadioCellGroup; cellGroup = createRadioCellGroup( null, columnGroup, 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 35298652e..a5fdc3ad5 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 @@ -123,3 +123,5 @@ export function createChartCellGroup( return cellGroup; } + +export type CreateChartCellGroup = typeof createChartCellGroup; diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts index 8069fe478..e6c86958c 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts @@ -233,3 +233,5 @@ function createCheckbox( return checkbox; } + +export type CreateCheckboxCellGroup = typeof createCheckboxCellGroup; diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts index 6fdd4ad66..9541b2b88 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts @@ -164,6 +164,8 @@ export function createImageCellGroup( return cellGroup; } +export type CreateImageCellGroup = typeof createImageCellGroup; + /** * 调整某个图片资源所在行列的行高列宽 之后重绘 * @param col diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/index.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/index.ts new file mode 100644 index 000000000..36930f41a --- /dev/null +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/index.ts @@ -0,0 +1,43 @@ +import { Factory } from '../../../core/factory'; +import { createChartCellGroup } from './chart-cell'; +import { createCheckboxCellGroup } from './checkbox-cell'; +import { createImageCellGroup } from './image-cell'; +import { createRadioCellGroup } from './radio-cell'; +import { createSparkLineCellGroup } from './spark-line-cell'; +import { createVideoCellGroup } from './video-cell'; +import { createCellGroup as createTextCellGroup } from './text-cell'; +import { createProgressBarCell } from './progress-bar-cell'; +import { getAxisDomainRangeAndLabels } from '../../../layout/chart-helper/get-axis-domain'; + +export const registerChartCell = () => { + Factory.registerFunction('createChartCellGroup', createChartCellGroup); + Factory.registerFunction('getAxisDomainRangeAndLabels', getAxisDomainRangeAndLabels); +}; + +export const registerCheckboxCell = () => { + Factory.registerFunction('createCheckboxCellGroup', createCheckboxCellGroup); +}; + +export const registerImageCell = () => { + Factory.registerFunction('createImageCellGroup', createImageCellGroup); +}; + +export const registerProgressBarCell = () => { + Factory.registerFunction('createProgressBarCell', createProgressBarCell); +}; + +export const registerRadioCell = () => { + Factory.registerFunction('createRadioCellGroup', createRadioCellGroup); +}; + +export const registerSparkLineCell = () => { + Factory.registerFunction('createSparkLineCellGroup', createSparkLineCellGroup); +}; + +export const registerTextCell = () => { + Factory.registerFunction('createTextCellGroup', createTextCellGroup); +}; + +export const registerVideoCell = () => { + Factory.registerFunction('createVideoCellGroup', createVideoCellGroup); +}; diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/progress-bar-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/progress-bar-cell.ts index a1e5bf61f..b19a32ddb 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/progress-bar-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/progress-bar-cell.ts @@ -560,3 +560,5 @@ export function createProgressBarCell( } return percentCompleteBarGroup; } + +export type CreateProgressBarCell = typeof createProgressBarCell; diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/radio-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/radio-cell.ts index eb30a4449..e62c97f9a 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/radio-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/radio-cell.ts @@ -98,6 +98,8 @@ export function createRadioCellGroup( return cellGroup; } +export type CreateRadioCellGroup = typeof createRadioCellGroup; + function createRadio( col: number, row: number, diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/spark-line-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/spark-line-cell.ts index 58dee58c5..a7fb271f8 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/spark-line-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/spark-line-cell.ts @@ -85,6 +85,8 @@ export function createSparkLineCellGroup( return cellGroup; } +export type CreateSparkLineCellGroup = typeof createSparkLineCellGroup; + function createSparkLine( col: number, row: number, diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts index 901d6afe6..c6cf7f6b3 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts @@ -179,6 +179,8 @@ export function createCellGroup( return cellGroup; } +export type CreateTextCellGroup = typeof createCellGroup; + // /** // * @description: 获取函数式赋值的样式,记录在cellTheme中 // * @param {BaseTableAPI} table diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts index c186a8376..20a0bd8fd 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts @@ -206,3 +206,5 @@ export function createVideoCellGroup( return cellGroup; } + +export type CreateVideoCellGroup = typeof createVideoCellGroup; diff --git a/packages/vtable/src/scenegraph/layout/update-height.ts b/packages/vtable/src/scenegraph/layout/update-height.ts index 25e30f1e0..bdc050d47 100644 --- a/packages/vtable/src/scenegraph/layout/update-height.ts +++ b/packages/vtable/src/scenegraph/layout/update-height.ts @@ -1,7 +1,7 @@ import type { ProgressBarStyle } from '../../body-helper/style/ProgressBarStyle'; import type { Group } from '../graphic/group'; -import { createProgressBarCell } from '../group-creater/cell-type/progress-bar-cell'; -import { createSparkLineCellGroup } from '../group-creater/cell-type/spark-line-cell'; +import type { CreateProgressBarCell } from '../group-creater/cell-type/progress-bar-cell'; +import type { CreateSparkLineCellGroup } from '../group-creater/cell-type/spark-line-cell'; import type { Scenegraph } from '../scenegraph'; import { getCellMergeInfo } from '../utils/get-cell-merge'; import { getProp } from '../utils/get-prop'; @@ -17,6 +17,7 @@ import { resizeCellGroup, getCustomCellMergeCustom } from '../group-creater/cell import type { IGraphic } from '@src/vrender'; import { getCellMergeRange } from '../../tools/merge-range'; import type { ColumnDefine } from '../../ts-types'; +import { Factory } from '../../core/factory'; export function updateRowHeight(scene: Scenegraph, row: number, detaY: number, skipTableHeightMap?: boolean) { // 更新table行高存储 @@ -139,6 +140,7 @@ export function updateCellHeight( const dataValue = scene.table.getCellOriginValue(col, row); const padding = getQuadProps(getProp('padding', style, col, row, scene.table)); + const createProgressBarCell = Factory.getFunction('createProgressBarCell') as CreateProgressBarCell; const newBarCell = createProgressBarCell( columnDefine, style, @@ -163,6 +165,7 @@ export function updateCellHeight( cell.removeAllChild(); const headerStyle = scene.table._getCellStyle(col, row); const padding = getQuadProps(getProp('padding', headerStyle, col, row, scene.table)); + const createSparkLineCellGroup = Factory.getFunction('createSparkLineCellGroup') as CreateSparkLineCellGroup; createSparkLineCellGroup( cell, cell.parent, diff --git a/packages/vtable/src/scenegraph/layout/update-width.ts b/packages/vtable/src/scenegraph/layout/update-width.ts index bc55e24e3..b5e246b09 100644 --- a/packages/vtable/src/scenegraph/layout/update-width.ts +++ b/packages/vtable/src/scenegraph/layout/update-width.ts @@ -8,8 +8,8 @@ import type { IProgressbarColumnBodyDefine } from '../../ts-types/list-table/def import { CUSTOM_CONTAINER_NAME, CUSTOM_MERGE_CONTAINER_NAME, dealWithCustom } from '../component/custom'; import type { Group } from '../graphic/group'; 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 type { CreateProgressBarCell } from '../group-creater/cell-type/progress-bar-cell'; +import type { CreateSparkLineCellGroup } from '../group-creater/cell-type/spark-line-cell'; import { resizeCellGroup, getCustomCellMergeCustom } from '../group-creater/cell-helper'; import type { Scenegraph } from '../scenegraph'; import { getCellMergeInfo } from '../utils/get-cell-merge'; @@ -279,6 +279,7 @@ function updateCellWidth( const dataValue = scene.table.getCellOriginValue(col, row); const padding = getQuadProps(getProp('padding', style, col, row, scene.table)); + const createProgressBarCell = Factory.getFunction('createProgressBarCell') as CreateProgressBarCell; const newBarCell = createProgressBarCell( columnDefine, style, @@ -303,6 +304,7 @@ function updateCellWidth( cellGroup.removeAllChild(); const headerStyle = scene.table._getCellStyle(col, row); const padding = getQuadProps(getProp('padding', headerStyle, col, row, scene.table)); + const createSparkLineCellGroup = Factory.getFunction('createSparkLineCellGroup') as CreateSparkLineCellGroup; createSparkLineCellGroup( cellGroup, cellGroup.parent, diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index 3ab8bdcaf..eb0cdeff7 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -72,7 +72,7 @@ import { dealWithAnimationAppear } from './animation/appear'; registerForVrender(); // VChart poptip theme -loadPoptip(); +// loadPoptip(); container.load(splitModule); container.load(textMeasureModule); // container.load(renderServiceModule); diff --git a/packages/vtable/src/vrender.ts b/packages/vtable/src/vrender.ts index 53581830d..4c650f2e4 100644 --- a/packages/vtable/src/vrender.ts +++ b/packages/vtable/src/vrender.ts @@ -1,3 +1,4 @@ +import { loadPoptip } from '@visactor/vrender-components'; import '@visactor/vrender-core'; import { container, isBrowserEnv, isNodeEnv, preLoadAllModule } from '@visactor/vrender-core'; import { @@ -57,6 +58,8 @@ export function registerForVrender() { registerSymbol(); registerText(); // registerWrapText(); + + loadPoptip(); } export { Direction } from '@visactor/vrender-core'; From 3ae44444214191f78e9a0599585aa2e5919c3d9a Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 27 Jun 2024 10:55:49 +0800 Subject: [PATCH 06/14] feat: split ListTableSimple & ListTableAll --- .../api/listTable-getCellRect.test.ts | 2 +- .../listTable-cellType-function.test.ts | 2 +- .../columns/listTable-cellType.test.ts | 2 +- .../columns/listTable-checkbox.test.ts | 2 +- .../columns/listTable-custom-layout.test.ts | 2 +- .../columns/listTable-dragHeader.test.ts | 2 +- .../components/listTable-menu.test.ts | 2 +- .../components/listTable-title.test.ts | 2 +- .../components/listTable-tooltip.test.ts | 2 +- .../vtable/__tests__/listTable-1W.test.ts | 2 +- packages/vtable/__tests__/listTable.test.ts | 2 +- .../options/listTable-api-with-frozen.test.ts | 2 +- .../options/listTable-autoRowHeight.test.ts | 2 +- .../options/listTable-frozen.test.ts | 2 +- .../__tests__/options/listTable-sort.test.ts | 2 +- packages/vtable/package.json | 9 ++++- packages/vtable/src/ListTable-all.ts | 36 +++++++++++++++++++ packages/vtable/src/ListTable-simple.ts | 5 +++ packages/vtable/src/ListTable.ts | 34 ++++++++++++++++++ packages/vtable/src/index.ts | 2 +- 20 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 packages/vtable/src/ListTable-all.ts create mode 100644 packages/vtable/src/ListTable-simple.ts diff --git a/packages/vtable/__tests__/api/listTable-getCellRect.test.ts b/packages/vtable/__tests__/api/listTable-getCellRect.test.ts index e304c94d5..dad3cca5d 100644 --- a/packages/vtable/__tests__/api/listTable-getCellRect.test.ts +++ b/packages/vtable/__tests__/api/listTable-getCellRect.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable import records from '../data/marketsales.json'; -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; describe('listTable getCellRect test', () => { diff --git a/packages/vtable/__tests__/columns/listTable-cellType-function.test.ts b/packages/vtable/__tests__/columns/listTable-cellType-function.test.ts index f72000e6f..6d6012798 100644 --- a/packages/vtable/__tests__/columns/listTable-cellType-function.test.ts +++ b/packages/vtable/__tests__/columns/listTable-cellType-function.test.ts @@ -1,6 +1,6 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; describe('listTable-cellType-function init test', () => { diff --git a/packages/vtable/__tests__/columns/listTable-cellType.test.ts b/packages/vtable/__tests__/columns/listTable-cellType.test.ts index 66e41abc1..f1629a7e9 100644 --- a/packages/vtable/__tests__/columns/listTable-cellType.test.ts +++ b/packages/vtable/__tests__/columns/listTable-cellType.test.ts @@ -1,6 +1,6 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; describe('listTable-cellType init test', () => { diff --git a/packages/vtable/__tests__/columns/listTable-checkbox.test.ts b/packages/vtable/__tests__/columns/listTable-checkbox.test.ts index f8dea8c89..246245068 100644 --- a/packages/vtable/__tests__/columns/listTable-checkbox.test.ts +++ b/packages/vtable/__tests__/columns/listTable-checkbox.test.ts @@ -1,6 +1,6 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; describe('listTable-checkbox init test', () => { diff --git a/packages/vtable/__tests__/columns/listTable-custom-layout.test.ts b/packages/vtable/__tests__/columns/listTable-custom-layout.test.ts index ded17c8c0..5f39d66d4 100644 --- a/packages/vtable/__tests__/columns/listTable-custom-layout.test.ts +++ b/packages/vtable/__tests__/columns/listTable-custom-layout.test.ts @@ -1,7 +1,7 @@ /* eslint-disable max-len */ // @ts-nocheck // 有问题可对照demo unitTestListTable -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import * as VTable from '../../src/index'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; diff --git a/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts b/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts index 03476fb4a..f7249a9e5 100644 --- a/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts +++ b/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts @@ -1,6 +1,6 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; const generatePersons = count => { diff --git a/packages/vtable/__tests__/components/listTable-menu.test.ts b/packages/vtable/__tests__/components/listTable-menu.test.ts index 9c3f94cec..16eba25e5 100644 --- a/packages/vtable/__tests__/components/listTable-menu.test.ts +++ b/packages/vtable/__tests__/components/listTable-menu.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable import records from '../data/marketsales.json'; -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; describe('listTable-menu init test', () => { diff --git a/packages/vtable/__tests__/components/listTable-title.test.ts b/packages/vtable/__tests__/components/listTable-title.test.ts index d90742892..aa0f73f55 100644 --- a/packages/vtable/__tests__/components/listTable-title.test.ts +++ b/packages/vtable/__tests__/components/listTable-title.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable import records from '../data/marketsales.json'; -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; describe('listTable-title init test', () => { diff --git a/packages/vtable/__tests__/components/listTable-tooltip.test.ts b/packages/vtable/__tests__/components/listTable-tooltip.test.ts index 057c4d0a0..f378fd162 100644 --- a/packages/vtable/__tests__/components/listTable-tooltip.test.ts +++ b/packages/vtable/__tests__/components/listTable-tooltip.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable import records from '../data/marketsales.json'; -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import * as VTable from '../../src/index'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; diff --git a/packages/vtable/__tests__/listTable-1W.test.ts b/packages/vtable/__tests__/listTable-1W.test.ts index c021d39e1..9c018758e 100644 --- a/packages/vtable/__tests__/listTable-1W.test.ts +++ b/packages/vtable/__tests__/listTable-1W.test.ts @@ -1,6 +1,6 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable -import { ListTable } from '../src/ListTable'; +import { ListTable } from '../src'; import * as VTable from '../src/index'; import { createDiv } from './dom'; global.__VERSION__ = 'none'; diff --git a/packages/vtable/__tests__/listTable.test.ts b/packages/vtable/__tests__/listTable.test.ts index b4215bcaf..d76fc9508 100644 --- a/packages/vtable/__tests__/listTable.test.ts +++ b/packages/vtable/__tests__/listTable.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable import records from './data/marketsales.json'; -import { ListTable } from '../src/ListTable'; +import { ListTable } from '../src'; import { createDiv } from './dom'; global.__VERSION__ = 'none'; describe('listTable init test', () => { diff --git a/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts b/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts index b4f5d8240..efc31f54c 100644 --- a/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts +++ b/packages/vtable/__tests__/options/listTable-api-with-frozen.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable import records from '../data/marketsales.json'; -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; describe('listTable init test', () => { diff --git a/packages/vtable/__tests__/options/listTable-autoRowHeight.test.ts b/packages/vtable/__tests__/options/listTable-autoRowHeight.test.ts index 58d100e07..a65b85c7d 100644 --- a/packages/vtable/__tests__/options/listTable-autoRowHeight.test.ts +++ b/packages/vtable/__tests__/options/listTable-autoRowHeight.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable import records from '../data/marketsales.json'; -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv, removeDom } from '../dom'; global.__VERSION__ = 'none'; describe('listTable-autoRowHeight init test', () => { diff --git a/packages/vtable/__tests__/options/listTable-frozen.test.ts b/packages/vtable/__tests__/options/listTable-frozen.test.ts index b11506591..47ed49543 100644 --- a/packages/vtable/__tests__/options/listTable-frozen.test.ts +++ b/packages/vtable/__tests__/options/listTable-frozen.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable import records from '../data/marketsales.json'; -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; describe('listTable-frozen init test', () => { diff --git a/packages/vtable/__tests__/options/listTable-sort.test.ts b/packages/vtable/__tests__/options/listTable-sort.test.ts index 8f956ac64..fcf83b756 100644 --- a/packages/vtable/__tests__/options/listTable-sort.test.ts +++ b/packages/vtable/__tests__/options/listTable-sort.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestListTable import records from '../data/marketsales.json'; -import { ListTable } from '../../src/ListTable'; +import { ListTable } from '../../src'; import { createDiv } from '../dom'; global.__VERSION__ = 'none'; describe('listTable init test', () => { diff --git a/packages/vtable/package.json b/packages/vtable/package.json index b8f63dc70..ccdcf4df4 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -19,7 +19,14 @@ "url": "https://VisActor.io/" }, "license": "MIT", - "sideEffects": true, + "sideEffects": [ + "./src/ListTable-all.js", + "./src/ListTable-simple.js", + "./src/PivotTable.js", + "./src/PivotChart.js", + "./src/index.js", + "./src/scenegraph/scenegraph.js" + ], "main": "cjs/index.js", "module": "es/index.js", "types": "es/index.d.ts", diff --git a/packages/vtable/src/ListTable-all.ts b/packages/vtable/src/ListTable-all.ts new file mode 100644 index 000000000..2d497ce21 --- /dev/null +++ b/packages/vtable/src/ListTable-all.ts @@ -0,0 +1,36 @@ +import { ListTable } from './ListTable'; +import { + registerAxis, + registerEmptyTip, + registerLegend, + registerMenu, + registerTitle, + registerTooltip +} from './components'; +import { + registerChartCell, + registerCheckboxCell, + registerImageCell, + registerProgressBarCell, + registerRadioCell, + registerSparkLineCell, + registerTextCell, + registerVideoCell +} from './scenegraph/group-creater/cell-type'; + +registerAxis(); +registerEmptyTip(); +registerLegend(); +registerMenu(); +registerTitle(); +registerTooltip(); + +registerChartCell(); +registerCheckboxCell(); +registerImageCell(); +registerProgressBarCell(); +registerRadioCell(); +registerSparkLineCell(); +registerTextCell(); +registerVideoCell(); +export class ListTableAll extends ListTable {} diff --git a/packages/vtable/src/ListTable-simple.ts b/packages/vtable/src/ListTable-simple.ts new file mode 100644 index 000000000..77d2e735e --- /dev/null +++ b/packages/vtable/src/ListTable-simple.ts @@ -0,0 +1,5 @@ +import { ListTable } from './ListTable'; +import { registerTextCell } from './scenegraph/group-creater/cell-type'; + +registerTextCell(); +export class ListTableSimple extends ListTable {} diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 01cbafab8..5e812da11 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -37,6 +37,40 @@ import { cloneDeepSpec } from '@visactor/vutils-extension'; import { setCellCheckboxState } from './state/checkbox/checkbox'; import type { IEmptyTipComponent } from './components/empty-tip/empty-tip'; import { Factory } from './core/factory'; +// import { +// registerAxis, +// registerEmptyTip, +// registerLegend, +// registerMenu, +// registerTitle, +// registerTooltip +// } from './components'; +// import { +// registerChartCell, +// registerCheckboxCell, +// registerImageCell, +// registerProgressBarCell, +// registerRadioCell, +// registerSparkLineCell, +// registerTextCell, +// registerVideoCell +// } from './scenegraph/group-creater/cell-type'; + +// registerAxis(); +// registerEmptyTip(); +// registerLegend(); +// registerMenu(); +// registerTitle(); +// registerTooltip(); + +// registerChartCell(); +// registerCheckboxCell(); +// registerImageCell(); +// registerProgressBarCell(); +// registerRadioCell(); +// registerSparkLineCell(); +// registerTextCell(); +// registerVideoCell(); export class ListTable extends BaseTable implements ListTableAPI { declare internalProps: ListTableProtected; diff --git a/packages/vtable/src/index.ts b/packages/vtable/src/index.ts index 7b75b93eb..28daa96df 100644 --- a/packages/vtable/src/index.ts +++ b/packages/vtable/src/index.ts @@ -30,7 +30,7 @@ import type { TextAlignType, TextBaselineType } from './ts-types'; -import { ListTable } from './ListTable'; +import { ListTableAll as ListTable } from './ListTable-all'; import { ListTableSimple } from './ListTable-simple'; import { PivotTable } from './PivotTable'; import { PivotChart } from './PivotChart'; From 57268062382484a36e16f94d43e9391fe5dacbff Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 27 Jun 2024 17:40:50 +0800 Subject: [PATCH 07/14] fix: change default axis label font size --- packages/vtable/src/components/axis/get-axis-attributes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vtable/src/components/axis/get-axis-attributes.ts b/packages/vtable/src/components/axis/get-axis-attributes.ts index 0f8222ace..542d39bf1 100644 --- a/packages/vtable/src/components/axis/get-axis-attributes.ts +++ b/packages/vtable/src/components/axis/get-axis-attributes.ts @@ -19,7 +19,7 @@ export const DEFAULT_TEXT_FONT_FAMILY = // eslint-disable-next-line max-len 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol'; -export const DEFAULT_TEXT_FONT_SIZE = 14; +export const DEFAULT_TEXT_FONT_SIZE = 12; export const THEME_CONSTANTS = { FONT_FAMILY: DEFAULT_TEXT_FONT_FAMILY, From 24757c96865472cf1aef1cb5a5517a988a5e44ac Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Thu, 27 Jun 2024 19:32:25 +0800 Subject: [PATCH 08/14] fix: fix axis config in chart spec --- .../src/components/axis/get-axis-attributes.ts | 2 +- .../src/layout/chart-helper/get-chart-spec.ts | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/vtable/src/components/axis/get-axis-attributes.ts b/packages/vtable/src/components/axis/get-axis-attributes.ts index 542d39bf1..0f8222ace 100644 --- a/packages/vtable/src/components/axis/get-axis-attributes.ts +++ b/packages/vtable/src/components/axis/get-axis-attributes.ts @@ -19,7 +19,7 @@ export const DEFAULT_TEXT_FONT_FAMILY = // eslint-disable-next-line max-len 'PingFang SC,Microsoft Yahei,system-ui,-apple-system,segoe ui,Roboto,Helvetica,Arial,sans-serif, apple color emoji,segoe ui emoji,segoe ui symbol'; -export const DEFAULT_TEXT_FONT_SIZE = 12; +export const DEFAULT_TEXT_FONT_SIZE = 14; export const THEME_CONSTANTS = { FONT_FAMILY: DEFAULT_TEXT_FONT_FAMILY, 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 5f49e97df..91c012394 100644 --- a/packages/vtable/src/layout/chart-helper/get-chart-spec.ts +++ b/packages/vtable/src/layout/chart-helper/get-chart-spec.ts @@ -7,6 +7,7 @@ import type { IChartIndicator, IIndicator } from '../../ts-types'; import { cloneDeepSpec } from '@visactor/vutils-extension'; import { Factory } from '../../core/factory'; import type { GetAxisDomainRangeAndLabels } from './get-axis-domain'; +import { DEFAULT_TEXT_FONT_SIZE } from '../../components/axis/get-axis-attributes'; const NO_AXISID_FRO_VTABLE = 'NO_AXISID_FRO_VTABLE'; @@ -212,14 +213,15 @@ export function getChartAxes(col: number, row: number, layout: PivotHeaderLayout axes.push( merge( { - range + range, + label: { style: { fontSize: DEFAULT_TEXT_FONT_SIZE } } }, axisOption, { type: axisOption?.type || 'linear', orient: index === 0 ? 'bottom' : 'top', // visible: true, - label: { visible: false }, + label: { visible: false, flush: true }, // label: { flush: true }, title: { visible: false }, domainLine: { visible: false }, @@ -247,7 +249,8 @@ export function getChartAxes(col: number, row: number, layout: PivotHeaderLayout merge( { domain: chartType === 'scatter' && !Array.isArray(domain) ? undefined : Array.from(domain ?? []), - range: chartType === 'scatter' && !Array.isArray(domain) ? domain : undefined + range: chartType === 'scatter' && !Array.isArray(domain) ? domain : undefined, + label: { style: { fontSize: DEFAULT_TEXT_FONT_SIZE } } }, axisOption, { @@ -309,14 +312,15 @@ export function getChartAxes(col: number, row: number, layout: PivotHeaderLayout axes.push( merge( { - range + range, + label: { style: { fontSize: DEFAULT_TEXT_FONT_SIZE } } }, axisOption, { type: axisOption?.type || 'linear', orient: index === 0 ? 'left' : 'right', // visible: true, - label: { visible: false }, + label: { visible: false, flush: true }, // label: { flush: true }, title: { visible: false }, domainLine: { visible: false }, @@ -346,7 +350,8 @@ export function getChartAxes(col: number, row: number, layout: PivotHeaderLayout merge( { domain: chartType === 'scatter' && !Array.isArray(domain) ? undefined : Array.from(domain ?? []), - range: chartType === 'scatter' && !Array.isArray(domain) ? domain : undefined + range: chartType === 'scatter' && !Array.isArray(domain) ? domain : undefined, + label: { style: { fontSize: DEFAULT_TEXT_FONT_SIZE } } }, axisOption, { From 9af438e672523a5f553e157e19c0815465ad1b89 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 28 Jun 2024 15:22:07 +0800 Subject: [PATCH 09/14] fix: fix lineCap for split stroke --- .../graphic/contributions/group-contribution-render.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts b/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts index c21c8aca4..8e6f90994 100644 --- a/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts +++ b/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts @@ -267,8 +267,9 @@ export function renderStroke( context.setStrokeStyle(group, group.attribute, x, y, groupAttribute); // if (isHighlight) { // context.setLineDash(highlightDash); - // context.lineCap = 'butt'; // } + const oldLineCap = context.lineCap; + context.lineCap = 'square'; const { lineDash = groupAttribute.lineDash } = group.attribute as any; // const lineDash = context.getLineDash(); @@ -413,6 +414,7 @@ export function renderStroke( context.stroke(); } context.lineDashOffset = 0; + context.lineCap = oldLineCap; context.setLineDash([]); } From e68228fac8cd057ee64496fc8c6a48ddf3947c21 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Fri, 28 Jun 2024 15:39:23 +0800 Subject: [PATCH 10/14] Revert "fix: fix lineCap for split stroke" This reverts commit 9af438e672523a5f553e157e19c0815465ad1b89. --- .../graphic/contributions/group-contribution-render.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts b/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts index 8e6f90994..c21c8aca4 100644 --- a/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts +++ b/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts @@ -267,9 +267,8 @@ export function renderStroke( context.setStrokeStyle(group, group.attribute, x, y, groupAttribute); // if (isHighlight) { // context.setLineDash(highlightDash); + // context.lineCap = 'butt'; // } - const oldLineCap = context.lineCap; - context.lineCap = 'square'; const { lineDash = groupAttribute.lineDash } = group.attribute as any; // const lineDash = context.getLineDash(); @@ -414,7 +413,6 @@ export function renderStroke( context.stroke(); } context.lineDashOffset = 0; - context.lineCap = oldLineCap; context.setLineDash([]); } From 9b2cb3574596c5318ac4400fcb085120026cfcd2 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 1 Jul 2024 14:59:45 +0800 Subject: [PATCH 11/14] feat: add PivotTableSample --- .../vtable/__tests__/edit/pivotTable.test.ts | 2 +- .../__tests__/pivotTable-analysis.test.ts | 2 +- .../vtable/__tests__/pivotTable-tree.test.ts | 2 +- packages/vtable/__tests__/pivotTable.test.ts | 2 +- packages/vtable/package.json | 3 +- packages/vtable/src/PivotTable-all.ts | 37 +++++++++++++++++++ packages/vtable/src/PivotTable-simple.ts | 6 +++ packages/vtable/src/PivotTable.ts | 34 ----------------- packages/vtable/src/index.ts | 5 ++- 9 files changed, 53 insertions(+), 40 deletions(-) create mode 100644 packages/vtable/src/PivotTable-all.ts create mode 100644 packages/vtable/src/PivotTable-simple.ts diff --git a/packages/vtable/__tests__/edit/pivotTable.test.ts b/packages/vtable/__tests__/edit/pivotTable.test.ts index 29042df2b..e4820e3f2 100644 --- a/packages/vtable/__tests__/edit/pivotTable.test.ts +++ b/packages/vtable/__tests__/edit/pivotTable.test.ts @@ -2,7 +2,7 @@ // @ts-nocheck // 有问题可对照demo unitTestPivotTable import records from '../data/marketsales.json'; -import { PivotTable } from '../../src/PivotTable'; +import { PivotTable } from '../../src'; import { register } from '../../src'; import { InputEditor } from '@visactor/vtable-editors'; import { createDiv } from '../dom'; diff --git a/packages/vtable/__tests__/pivotTable-analysis.test.ts b/packages/vtable/__tests__/pivotTable-analysis.test.ts index 37ceeaaba..ffff4f483 100644 --- a/packages/vtable/__tests__/pivotTable-analysis.test.ts +++ b/packages/vtable/__tests__/pivotTable-analysis.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck // 有问题可对照demo unitTestPivotTable import records from './data/marketsales.json'; -import { PivotTable } from '../src/PivotTable'; +import { PivotTable } from '../src'; import * as VTable from '../src/index'; import { createDiv } from './dom'; global.__VERSION__ = 'none'; diff --git a/packages/vtable/__tests__/pivotTable-tree.test.ts b/packages/vtable/__tests__/pivotTable-tree.test.ts index be6143c2f..38531f7c2 100644 --- a/packages/vtable/__tests__/pivotTable-tree.test.ts +++ b/packages/vtable/__tests__/pivotTable-tree.test.ts @@ -2,7 +2,7 @@ // @ts-nocheck // 有问题可对照demo unitTestPivotTable import records from './data/North_American_Superstore_pivot_extension_rows.json'; -import { PivotTable } from '../src/PivotTable'; +import { PivotTable } from '../src'; import { createDiv } from './dom'; global.__VERSION__ = 'none'; describe('pivotTableTree init test', () => { diff --git a/packages/vtable/__tests__/pivotTable.test.ts b/packages/vtable/__tests__/pivotTable.test.ts index 8f0dde9ee..9fcf5b10c 100644 --- a/packages/vtable/__tests__/pivotTable.test.ts +++ b/packages/vtable/__tests__/pivotTable.test.ts @@ -2,7 +2,7 @@ // @ts-nocheck // 有问题可对照demo unitTestPivotTable import records from './data/marketsales.json'; -import { PivotTable } from '../src/PivotTable'; +import { PivotTable } from '../src'; import { createDiv } from './dom'; global.__VERSION__ = 'none'; describe('pivotTable init test', () => { diff --git a/packages/vtable/package.json b/packages/vtable/package.json index ccdcf4df4..a1e6a88bd 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -22,7 +22,8 @@ "sideEffects": [ "./src/ListTable-all.js", "./src/ListTable-simple.js", - "./src/PivotTable.js", + "./src/PivotTable-all.js", + "./src/PivotTable-simple.js", "./src/PivotChart.js", "./src/index.js", "./src/scenegraph/scenegraph.js" diff --git a/packages/vtable/src/PivotTable-all.ts b/packages/vtable/src/PivotTable-all.ts new file mode 100644 index 000000000..caafdc0ed --- /dev/null +++ b/packages/vtable/src/PivotTable-all.ts @@ -0,0 +1,37 @@ +import { PivotTable } from './PivotTable'; +import { + registerAxis, + registerEmptyTip, + registerLegend, + registerMenu, + registerTitle, + registerTooltip +} from './components'; +import { + registerChartCell, + registerCheckboxCell, + registerImageCell, + registerProgressBarCell, + registerRadioCell, + registerSparkLineCell, + registerTextCell, + registerVideoCell +} from './scenegraph/group-creater/cell-type'; + +registerAxis(); +registerEmptyTip(); +registerLegend(); +registerMenu(); +registerTitle(); +registerTooltip(); + +registerChartCell(); +registerCheckboxCell(); +registerImageCell(); +registerProgressBarCell(); +registerRadioCell(); +registerSparkLineCell(); +registerTextCell(); +registerVideoCell(); + +export class PivotTableAll extends PivotTable {} diff --git a/packages/vtable/src/PivotTable-simple.ts b/packages/vtable/src/PivotTable-simple.ts new file mode 100644 index 000000000..f95c2e2a1 --- /dev/null +++ b/packages/vtable/src/PivotTable-simple.ts @@ -0,0 +1,6 @@ +import { PivotTable } from './PivotTable'; +import { registerTextCell } from './scenegraph/group-creater/cell-type'; + +registerTextCell(); + +export class PivotTableSimple extends PivotTable {} diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 2c750ee22..fa85cd30d 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -45,40 +45,6 @@ import { cloneDeepSpec } from '@visactor/vutils-extension'; import { parseColKeyRowKeyForPivotTable, supplementIndicatorNodesForCustomTree } from './layout/layout-helper'; import type { IEmptyTipComponent } from './components/empty-tip/empty-tip'; import { Factory } from './core/factory'; -import { - registerAxis, - registerEmptyTip, - registerLegend, - registerMenu, - registerTitle, - registerTooltip -} from './components'; -import { - registerChartCell, - registerCheckboxCell, - registerImageCell, - registerProgressBarCell, - registerRadioCell, - registerSparkLineCell, - registerTextCell, - registerVideoCell -} from './scenegraph/group-creater/cell-type'; - -registerAxis(); -registerEmptyTip(); -registerLegend(); -registerMenu(); -registerTitle(); -registerTooltip(); - -registerChartCell(); -registerCheckboxCell(); -registerImageCell(); -registerProgressBarCell(); -registerRadioCell(); -registerSparkLineCell(); -registerTextCell(); -registerVideoCell(); export class PivotTable extends BaseTable implements PivotTableAPI { layoutNodeId: { seqId: number } = { seqId: 0 }; diff --git a/packages/vtable/src/index.ts b/packages/vtable/src/index.ts index 28daa96df..e0bd280d9 100644 --- a/packages/vtable/src/index.ts +++ b/packages/vtable/src/index.ts @@ -32,7 +32,9 @@ import type { } from './ts-types'; import { ListTableAll as ListTable } from './ListTable-all'; import { ListTableSimple } from './ListTable-simple'; -import { PivotTable } from './PivotTable'; +// import { PivotTable } from './PivotTable'; +import { PivotTableAll as PivotTable } from './PivotTable-all'; +import { PivotTableSimple } from './PivotTable-simple'; import { PivotChart } from './PivotChart'; import type { MousePointerCellEvent } from './ts-types/events'; import * as CustomLayout from './render/layout'; @@ -65,6 +67,7 @@ export { ListTableSimple, ListTableConstructorOptions, PivotTable, + PivotTableSimple, PivotTableConstructorOptions, PivotChartConstructorOptions, PivotChart, From e0cdef4f8632ce0557b4ebded0dcd135f8a2e7f9 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Mon, 1 Jul 2024 15:08:38 +0800 Subject: [PATCH 12/14] chore: add rush change log --- .../vtable/feat-package-size_2024-07-01-07-08.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@visactor/vtable/feat-package-size_2024-07-01-07-08.json diff --git a/common/changes/@visactor/vtable/feat-package-size_2024-07-01-07-08.json b/common/changes/@visactor/vtable/feat-package-size_2024-07-01-07-08.json new file mode 100644 index 000000000..907cf92ad --- /dev/null +++ b/common/changes/@visactor/vtable/feat-package-size_2024-07-01-07-08.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "feat: optimize package size", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file From 4ef4d4ae84f9176d21f6fa19ca5824cb4a1455c6 Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Tue, 2 Jul 2024 10:58:15 +0800 Subject: [PATCH 13/14] docs: add load on demand docs --- .../feat-package-size_2024-07-01-07-08.json | 4 +- docs/assets/guide/en/Load_on_Demand.md | 38 +++++++++++++++++++ docs/assets/guide/menu.json | 7 ++++ docs/assets/guide/zh/Load_on_Demand.md | 38 +++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 docs/assets/guide/en/Load_on_Demand.md create mode 100644 docs/assets/guide/zh/Load_on_Demand.md diff --git a/common/changes/@visactor/vtable/feat-package-size_2024-07-01-07-08.json b/common/changes/@visactor/vtable/feat-package-size_2024-07-01-07-08.json index 907cf92ad..b30ef2bad 100644 --- a/common/changes/@visactor/vtable/feat-package-size_2024-07-01-07-08.json +++ b/common/changes/@visactor/vtable/feat-package-size_2024-07-01-07-08.json @@ -2,8 +2,8 @@ "changes": [ { "packageName": "@visactor/vtable", - "comment": "feat: optimize package size", - "type": "none" + "comment": "feat: optimize package size & add load on demand feature", + "type": "minor" } ], "packageName": "@visactor/vtable" diff --git a/docs/assets/guide/en/Load_on_Demand.md b/docs/assets/guide/en/Load_on_Demand.md new file mode 100644 index 000000000..c004ec40e --- /dev/null +++ b/docs/assets/guide/en/Load_on_Demand.md @@ -0,0 +1,38 @@ +# VTable on-demand loading + +By default, `ListTable`, `PivotTable` and `PivotChart` introduced from `@visactor/vtable` package contain all table-related components, which is a complete table component library. + +In order to meet the needs of package size optimization, VTable provides two types, `ListTableSimple` and `PivotTableSimple`, which are the most simplified lists and pivot tables, respectively. They only support text display and do not contain external components such as menus and titles. If you need some functions, you can load them on demand. The usage is as follows: + +```js +// ListTableSimple, PivotTableSimple are the simplest list and pivot table components, which do not include cell types and any components other than text +import {ListTableSimple, PivotTableSimple, registerTitle, registerTooltip} from '@visactor/vtable'; + +// Register title component +registerTitle(); + +// Register tooltip component +registerTooltip(); +``` + +## Load functions on demand + +### Functional components + +* registerAxis: axis component +* registerEmptyTip: empty prompt component +* registerLegend: legend component +* registerMenu: menu component +* registerTitle: title component +* registerTooltip: tooltip component + +### Cell type + +* registerChartCell: chart cell +* registerCheckboxCell: checkbox cell +* registerImageCell: Image cell +* registerProgressBarCell: Progress bar cell +* registerRadioCell: Radio button cell +* registerSparkLineCell: Sparkline cell +* registerTextCell: Text cell +* registerVideoCell: Video cell \ No newline at end of file diff --git a/docs/assets/guide/menu.json b/docs/assets/guide/menu.json index 09a36a953..3fa5404a5 100644 --- a/docs/assets/guide/menu.json +++ b/docs/assets/guide/menu.json @@ -551,6 +551,13 @@ } ] }, + { + "path": "Load_on_Demand", + "title": { + "zh": "按需加载", + "en": "Load on Demand" + } + }, { "path": "search", "title": { diff --git a/docs/assets/guide/zh/Load_on_Demand.md b/docs/assets/guide/zh/Load_on_Demand.md new file mode 100644 index 000000000..3f834daa3 --- /dev/null +++ b/docs/assets/guide/zh/Load_on_Demand.md @@ -0,0 +1,38 @@ +# VTable 按需加载 + +默认从 `@visactor/vtable` 包中引入的 `ListTable` 、 `PivotTable` 和 `PivotChart` 包含所有的表格相关的组件,是一个完整的表格组件库。 + +为了满足包体积优化的需求,VTable提供了 `ListTableSimple` 和 `PivotTableSimple` 两个类型,分别是最简化的列表和透视表,只支持文字类型的显示,不包含菜单、标题等外部组件。如果需要部分功能,可以进行按需加载,使用方法如下: + +```js +// ListTableSimple, PivotTableSimple 是最简单的列表和透视表组件,不包除了文字之外的单元格类型和任何组件 +import {ListTableSimple, PivotTableSimple, registerTitle, registerTooltip} from '@visactor/vtable'; + +// 注册标题组件 +registerTitle(); + +// 注册tooltip组件 +registerTooltip(); +``` + +## 按需加载功能 + +### 功能组件 + +* registerAxis: 坐标轴组件 +* registerEmptyTip: 空白提示组件 +* registerLegend: 图例组件 +* registerMenu: 菜单组件 +* registerTitle: 标题组件 +* registerTooltip: tooltip组件 + +### 单元格类型 + +* registerChartCell: 图表单元格 +* registerCheckboxCell: 复选框单元格 +* registerImageCell: 图片单元格 +* registerProgressBarCell: 进度条单元格 +* registerRadioCell: 单选框单元格 +* registerSparkLineCell: 迷你图单元格 +* registerTextCell: 文字单元格 +* registerVideoCell: 视频单元格 \ No newline at end of file From 0f8887b2fb785a4586fbb66962ecf03721053b7a Mon Sep 17 00:00:00 2001 From: Rui-Sun Date: Wed, 3 Jul 2024 16:34:20 +0800 Subject: [PATCH 14/14] Merge origin/develop into feat/package-size --- CONTRIBUTING.md | 9 + CONTRIBUTING.zh-CN.md | 13 +- .../feat-export-format_2024-07-01-13-15.json | 10 + .../fix-sort-update_2024-07-01-12-15.json | 10 + common/config/rush/pnpm-lock.yaml | 2 + common/config/rush/version-policies.json | 2 +- docs/assets/changelog/en/important-release.md | 37 ++ docs/assets/changelog/menu.json | 7 + docs/assets/changelog/zh/important-release.md | 39 ++ .../pivot-table-corner-title.md | 171 ++++++ .../demo/en/cell-type/pivot-sparkline.md | 133 +++++ .../pivot-analysis-sort-indicator.md | 5 +- .../demo/en/export/table-export-format.md | 204 ++------ .../en/export/table-export-ignore-icon.md | 200 +++++-- docs/assets/demo/menu.json | 14 + .../pivot-table-corner-title.md | 171 ++++++ .../demo/zh/cell-type/pivot-sparkline.md | 133 +++++ .../pivot-analysis-sort-indicator.md | 5 +- .../demo/zh/export/table-export-format.md | 204 ++------ .../zh/export/table-export-ignore-icon.md | 200 +++++-- docs/assets/guide/en/Contribution_Guide.md | 10 + .../data_analysis/pivot_table_dataAnalysis.md | 7 + docs/assets/guide/en/export/excel.md | 34 +- docs/assets/guide/zh/Contribution_Guide.md | 10 + .../data_analysis/pivot_table_dataAnalysis.md | 7 + docs/assets/guide/zh/export/excel.md | 33 +- .../option/en/column/base-column-type.md | 7 +- docs/assets/option/en/common/pivot-corner.md | 4 +- .../option/zh/column/base-column-type.md | 8 +- docs/assets/option/zh/common/pivot-corner.md | 11 +- docs/package.json | 4 +- packages/openinula-vtable/package.json | 2 +- packages/react-vtable/package.json | 2 +- packages/vtable-editors/package.json | 2 +- packages/vtable-export/package.json | 2 +- packages/vtable-export/src/excel/index.ts | 23 +- packages/vtable-search/package.json | 2 +- packages/vtable/CHANGELOG.json | 45 ++ packages/vtable/CHANGELOG.md | 34 +- .../listTable-getCellRelativePosition.test.ts | 253 +++++++++ packages/vtable/examples/list/list.ts | 3 + packages/vtable/examples/type/checkbox.ts | 3 +- packages/vtable/package.json | 2 +- packages/vtable/src/ListTable.ts | 4 +- packages/vtable/src/PivotTable.ts | 240 +++++---- .../tooltip/logic/BubbleTooltipElement.ts | 8 +- packages/vtable/src/core/BaseTable.ts | 306 ++--------- .../src/core/utils/get-cell-position.ts | 415 +++++++++++++++ packages/vtable/src/edit/edit-manager.ts | 12 +- packages/vtable/src/event/event.ts | 4 +- .../src/event/listener/container-dom.ts | 50 +- .../vtable/src/event/listener/table-group.ts | 29 +- packages/vtable/src/event/media-click.ts | 5 + packages/vtable/src/index.ts | 6 +- packages/vtable/src/layout/layout-helper.ts | 22 +- .../vtable/src/layout/pivot-header-layout.ts | 488 ++++++++++++------ .../vtable/src/layout/simple-header-layout.ts | 61 ++- .../vtable/src/scenegraph/component/custom.ts | 6 +- .../group-contribution-render.ts | 173 ++++--- .../scenegraph/group-creater/cell-helper.ts | 27 +- .../group-creater/cell-type/checkbox-cell.ts | 79 ++- .../group-creater/cell-type/image-cell.ts | 102 +++- .../group-creater/cell-type/video-cell.ts | 56 ++ .../group-creater/progress/proxy.ts | 12 +- .../update-position/sort-horizontal.ts | 12 +- .../progress/update-position/sort-vertical.ts | 44 +- .../scenegraph/layout/compute-col-width.ts | 2 +- .../src/scenegraph/layout/update-height.ts | 2 +- .../src/scenegraph/layout/update-width.ts | 2 +- packages/vtable/src/scenegraph/scenegraph.ts | 2 +- .../src/scenegraph/utils/text-icon-layout.ts | 299 ++++++++--- packages/vtable/src/state/cell-move/index.ts | 3 +- packages/vtable/src/state/sort/index.ts | 2 + packages/vtable/src/state/state.ts | 10 +- packages/vtable/src/tools/util.ts | 10 + packages/vtable/src/ts-types/base-table.ts | 3 + .../list-table/define/basic-define.ts | 2 + packages/vtable/src/ts-types/table-engine.ts | 2 +- 78 files changed, 3354 insertions(+), 1223 deletions(-) create mode 100644 common/changes/@visactor/vtable/feat-export-format_2024-07-01-13-15.json create mode 100644 common/changes/@visactor/vtable/fix-sort-update_2024-07-01-12-15.json create mode 100644 docs/assets/changelog/en/important-release.md create mode 100644 docs/assets/changelog/zh/important-release.md create mode 100644 docs/assets/demo/en/basic-functionality/pivot-table-corner-title.md create mode 100644 docs/assets/demo/en/cell-type/pivot-sparkline.md create mode 100644 docs/assets/demo/zh/basic-functionality/pivot-table-corner-title.md create mode 100644 docs/assets/demo/zh/cell-type/pivot-sparkline.md create mode 100644 packages/vtable/__tests__/api/listTable-getCellRelativePosition.test.ts create mode 100644 packages/vtable/src/core/utils/get-cell-position.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3514f410a..befd5255b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -204,6 +204,15 @@ $ rush change-all 3. Submit all code and create a Pull Request on Github, inviting others to review it. +### Promotion Task Contribution Guide +A promotion task refers to the action of publicly releasing materials related to VisActor, such as articles, demos, videos, etc., across various media channels. + +You can create a new issue, select the type others and tag it with promotion. Then, post it along with relevant links, screenshots, summaries, etc. + +For example, see https://github.com/VisActor/VChart/issues/2858. + +Every quarter, we will select some promotional works for VisActor and provide the authors with material rewards. + ## Embrace the VisActor Community In addition to contributing code to VisActor, we encourage you to participate in other activities that will make the community more prosperous, such as: diff --git a/CONTRIBUTING.zh-CN.md b/CONTRIBUTING.zh-CN.md index 56d3eda45..6b2fb9591 100644 --- a/CONTRIBUTING.zh-CN.md +++ b/CONTRIBUTING.zh-CN.md @@ -212,7 +212,18 @@ $ rush docs $ rush change-all ``` -3. 提交所有代码,并在 Github 创建 Pull Request,邀请其他人进行 review + +4. 提交所有代码,并在 Github 创建 Pull Request,邀请其他人进行 review + + + +### 推广任务贡献指南 + +推广任务是指你将和VisActor相关的文章、demo、视频 等素材,公开发布到各种媒体渠道的行为。 +你可以新建一个 issue,类型选择 `others` 打上 `promotion` 的标签,然后将相关链接,截图,简介等一起发布即可。 +例如:[https://github.com/VisActor/VChart/issues/2858](https://github.com/VisActor/VChart/issues/2858) + +每个季度我们会评选一些推广VisActor的作品,给予作者一定的物质奖励。 ## 拥抱 VisActor 社区 diff --git a/common/changes/@visactor/vtable/feat-export-format_2024-07-01-13-15.json b/common/changes/@visactor/vtable/feat-export-format_2024-07-01-13-15.json new file mode 100644 index 000000000..08f0fb65a --- /dev/null +++ b/common/changes/@visactor/vtable/feat-export-format_2024-07-01-13-15.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "feat: add formatExcelJSCell config in vtable-export #1989", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/common/changes/@visactor/vtable/fix-sort-update_2024-07-01-12-15.json b/common/changes/@visactor/vtable/fix-sort-update_2024-07-01-12-15.json new file mode 100644 index 000000000..1414d72c4 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-sort-update_2024-07-01-12-15.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix: fix frozen cell update problem in sort #1997", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 2e185a281..2b8ada8ae 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -22,6 +22,7 @@ importers: '@visactor/vtable-editors': workspace:* '@visactor/vtable-export': workspace:* '@visactor/vtable-search': workspace:* + '@visactor/vutils': ~0.18.9 '@vitejs/plugin-react': 3.1.0 axios: ^1.4.0 buble: ^0.20.0 @@ -50,6 +51,7 @@ importers: '@visactor/vtable-editors': link:../packages/vtable-editors '@visactor/vtable-export': link:../packages/vtable-export '@visactor/vtable-search': link:../packages/vtable-search + '@visactor/vutils': 0.18.9 axios: 1.7.2 buble: 0.20.0 highlight.js: 11.9.0 diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 23b368d9d..d59a4eeae 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -1 +1 @@ -[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"1.4.1","mainProject":"@visactor/vtable","nextBump":"patch"}] +[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"1.4.2","mainProject":"@visactor/vtable","nextBump":"patch"}] diff --git a/docs/assets/changelog/en/important-release.md b/docs/assets/changelog/en/important-release.md new file mode 100644 index 000000000..3c7536db4 --- /dev/null +++ b/docs/assets/changelog/en/important-release.md @@ -0,0 +1,37 @@ +# v1.0.0 + +2024-05-21 + +**💥 Breaking change** + +- **@visactor/vtable**: If the user has previously passed in rowTree and columnTree for the pivot table, under this usage, the result returned by the getCellOriginRecord interface changes from the previous object to an array structure, and if no default aggregation was previously performed, the SUM aggregation rule will be used for data calculation. If you want to cancel the numerical calculation rule, you can specify the aggregation rule as NONE for the indicator. + +Configuration examples, you can also refer to [Tutorial](https://visactor.io/vtable/guide/data_analysis/pivot_table_dataAnalysis): +``` +records:[{ + region: '中南', + province: '广西', + year: '2016', + quarter: '2016-Q1', + sales: 'NULL', + profit: 1546 +}], +dataConfig:{ + aggregationRules: [ + { + indicatorKey: 'sales', + field: 'sales', + aggregationType: VTable.TYPES.AggregationType.NONE, + } + ] +} + +``` +**🆕 New feature** + +- **@visactor/vtable**: rows and tree can combined use [#1644](https://github.com/VisActor/VTable/issues/1644) +- **@visactor/vtable**: add virtual option for rowTree and columnTree [#1644](https://github.com/VisActor/VTable/issues/1644) + + + +[more detail about v1.0.0](https://github.com/VisActor/VTable/releases/tag/v1.0.0) diff --git a/docs/assets/changelog/menu.json b/docs/assets/changelog/menu.json index f62327d9e..09f6defcc 100644 --- a/docs/assets/changelog/menu.json +++ b/docs/assets/changelog/menu.json @@ -1,6 +1,13 @@ { "menu": "changelog", "children": [ + { + "path": "important-release", + "title": { + "zh": "重大版本", + "en": "Important Release" + } + }, { "path": "release", "title": { diff --git a/docs/assets/changelog/zh/important-release.md b/docs/assets/changelog/zh/important-release.md new file mode 100644 index 000000000..7e9654242 --- /dev/null +++ b/docs/assets/changelog/zh/important-release.md @@ -0,0 +1,39 @@ + +# v1.0.0 + +2024-05-21 + +**💥 Breaking change** + +- **@visactor/vtable**: 透视表如果之前用户传入了rowTree和columnTree,在此用法下,getCellOriginRecord接口返回结果由之前对象变为数组结构,并且之前没有做默认聚合目前会使用SUM聚会规则进行数据计算,如果想取消数值计算规则可以为指标指定聚合规则为NONE。 + +配置示例,也可以参考[教程](https://visactor.io/vtable/guide/data_analysis/pivot_table_dataAnalysis): +``` +records:[{ + region: '中南', + province: '广西', + year: '2016', + quarter: '2016-Q1', + sales: 'NULL', + profit: 1546 +}], +dataConfig:{ + aggregationRules: [ + { + indicatorKey: 'sales', //指标名称 + field: 'sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.NONE, //不做聚合 匹配到其中对应数据获取其对应field的值 + } + ] +} + +``` + +**🆕 新增功能** + +- **@visactor/vtable**: 自定义树形表头customTree可以和透视分析能力结合使用 [#1644](https://github.com/VisActor/VTable/issues/1644) +- **@visactor/vtable**: 在 rowTree & columnTree 中加入virtual option [#1644](https://github.com/VisActor/VTable/issues/1644) + + + +[更多详情请查看 v1.0.0](https://github.com/VisActor/VTable/releases/tag/v1.0.0) diff --git a/docs/assets/demo/en/basic-functionality/pivot-table-corner-title.md b/docs/assets/demo/en/basic-functionality/pivot-table-corner-title.md new file mode 100644 index 000000000..dc7599f6d --- /dev/null +++ b/docs/assets/demo/en/basic-functionality/pivot-table-corner-title.md @@ -0,0 +1,171 @@ +--- +category: examples +group: Basic Features +title: Display dimension names in pivot table headers +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-table-corner-title.png +link: '../guide/table_type/Pivot_table/pivot_table_useage' +option: PivotTable#corner +--- + +# Display dimension names in pivot table headers + +If you set the header title display content basis to `'all'`, the header cell content will be the concatenation of the row dimension name and the column dimension name. + +titleOnDimension The corner title displays content based on: + +- 'column' column dimension name as header cell content +- 'row' row dimension name as header cell content +- 'none' means the header cell content is empty +- 'all' means the header cell content is the concatenation of the row dimension name and the column dimension name + +## Key Configurations + +- `PivotTable` +- `columns` +- `rows` +- `indicators` +- `corner.titleOnDimension` Corner title display content based on + +## Code Demo + +```javascript livedemo template=vtable +let tableInstance; +fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_Pivot_Chart_data.json') + .then(res => res.json()) + .then(data => { + const option = { + records: data, + rows: [ + { + dimensionKey: 'Category', + title: 'Category', + headerStyle: { + textStick: true, + bgColor(arg) { + if (arg.dataValue === 'Row Totals') { + return '#ff9900'; + } + return '#ECF1F5'; + } + }, + width: 'auto' + }, + { + dimensionKey: 'Sub-Category', + title: 'Sub-Catogery', + headerStyle: { + textStick: true + }, + width: 'auto' + } + ], + columns: [ + { + dimensionKey: 'Region', + title: 'Region', + headerStyle: { + textStick: true + }, + width: 'auto' + }, + { + dimensionKey: 'Segment', + title: 'Segment', + headerStyle: { + textStick: true + }, + width: 'auto' + } + ], + indicators: [ + { + indicatorKey: 'Quantity', + title: 'Quantity', + width: 'auto', + sort: true, + headerStyle: { + fontWeight: 'normal' + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return 'black'; + return 'red'; + }, + bgColor(arg) { + const rowHeaderPaths = arg.cellHeaderPaths.rowHeaderPaths; + if (rowHeaderPaths?.[1]?.value === 'Sub Totals') { + return '#ba54ba'; + } else if (rowHeaderPaths?.[0]?.value === 'Row Totals') { + return '#ff9900'; + } + return undefined; + } + } + }, + { + indicatorKey: 'Sales', + title: 'Sales', + width: 'auto', + sort: true, + headerStyle: { + fontWeight: 'normal' + }, + format: rec => { + return '$' + Number(rec).toFixed(2); + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return 'black'; + return 'red'; + }, + bgColor(arg) { + const rowHeaderPaths = arg.cellHeaderPaths.rowHeaderPaths; + if (rowHeaderPaths?.[1]?.value === 'Sub Totals') { + return '#ba54ba'; + } else if (rowHeaderPaths?.[0]?.value === 'Row Totals') { + return '#ff9900'; + } + return undefined; + } + } + }, + { + indicatorKey: 'Profit', + title: 'Profit', + width: 'auto', + showSort: false, + headerStyle: { + fontWeight: 'normal' + }, + format: rec => { + return '$' + Number(rec).toFixed(2); + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return 'black'; + return 'red'; + }, + bgColor(arg) { + const rowHeaderPaths = arg.cellHeaderPaths.rowHeaderPaths; + if (rowHeaderPaths?.[1]?.value === 'Sub Totals') { + return '#ba54ba'; + } else if (rowHeaderPaths?.[0]?.value === 'Row Totals') { + return '#ff9900'; + } + return undefined; + } + } + } + ], + corner: { + titleOnDimension: 'all' + }, + widthMode: 'standard' + }; + tableInstance = new VTable.PivotTable(document.getElementById(CONTAINER_ID), option); + window['tableInstance'] = tableInstance; + }); +``` diff --git a/docs/assets/demo/en/cell-type/pivot-sparkline.md b/docs/assets/demo/en/cell-type/pivot-sparkline.md new file mode 100644 index 000000000..c48a4423a --- /dev/null +++ b/docs/assets/demo/en/cell-type/pivot-sparkline.md @@ -0,0 +1,133 @@ +--- +category: examples +group: Cell Type +title: PivotTable display sparkline +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-sparkline.png +link: '../guide/cell_type/chart' +option: PivotTable-indicators-chart#cellType +--- + +# PivotTable display sparkline + +Display the data corresponding to the cell in the form of a mini chart. + +## Key Configurations + +- `cellType: 'sparkline'` specifies the type of chart +- `sparklineSpec: {}` Sparkline spec +- `dataConfig.aggregationRules` configures aggregation rules. The rule used here is of `RECORD` type, which means that the source data record of a cell needs to be collected as the data source of the mini chart + +## Code demo + +```javascript livedemo template=vtable +VTable.register.chartModule('vchart', VChart); +let tableInstance; +fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_data.json') + .then(res => res.json()) + .then(data => { + const columns = [ + { + dimensionKey: 'Region', + title: 'Region', + headerStyle: { + textStick: true + } + } + ]; + const rows = ['Category']; + const indicators = [ + { + indicatorKey: 'Sales', + title: 'Sales', + width: 120, + format: rec => { + return '$' + Number(rec).toFixed(2); + } + }, + { + indicatorKey: 'SalesRecords', + title: 'Sales Trend', + cellType: 'sparkline', + width: 500, + sparklineSpec: { + type: 'line', + xField: 'Order Date', + yField: 'Sales', + pointShowRule: 'none', + smooth: true, + line: { + style: { + stroke: '#2E62F1', + strokeWidth: 2 + // interpolate: 'monotone', + } + }, + point: { + hover: { + stroke: 'blue', + strokeWidth: 1, + fill: 'red', + shape: 'circle', + size: 4 + }, + style: { + stroke: 'red', + strokeWidth: 1, + fill: 'yellow', + shape: 'circle', + size: 2 + } + }, + crosshair: { + style: { + stroke: 'gray', + strokeWidth: 1 + } + } + } + } + ]; + const option = { + dataConfig: { + aggregationRules: [ + //做聚合计算的依据,如销售额如果没有配置则默认按聚合sum计算结果显示单元格内容 + { + indicatorKey: 'SalesRecords', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.RECORD //计算类型 + } + ] + }, + rows, + columns, + indicators, + indicatorsAsCol: true, + records: data, + defaultRowHeight: 80, + defaultHeaderRowHeight: 50, + defaultColWidth: 280, + defaultHeaderColWidth: 130, + indicatorTitle: '指标', + autoWrapText: true, + // widthMode:'adaptive', + // heightMode:'adaptive', + corner: { + titleOnDimension: 'row', + headerStyle: { + autoWrapText: true + } + } + }; + + tableInstance = new VTable.PivotTable(document.getElementById(CONTAINER_ID), option); + const { LEGEND_ITEM_CLICK } = VTable.ListTable.EVENT_TYPE; + window.tableInstance = tableInstance; + tableInstance.onVChartEvent('click', args => { + console.log('onVChartEvent click', args); + }); + tableInstance.onVChartEvent('mouseover', args => { + console.log('onVChartEvent mouseover', args); + }); + window.tableInstance = tableInstance; + }); +``` diff --git a/docs/assets/demo/en/data-analysis/pivot-analysis-sort-indicator.md b/docs/assets/demo/en/data-analysis/pivot-analysis-sort-indicator.md index 71c09232e..c372487e6 100644 --- a/docs/assets/demo/en/data-analysis/pivot-analysis-sort-indicator.md +++ b/docs/assets/demo/en/data-analysis/pivot-analysis-sort-indicator.md @@ -154,10 +154,7 @@ fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American } ], corner: { - titleOnDimension: 'row', - headerStyle: { - textStick: true - } + titleOnDimension: 'row' }, dataConfig: { sortRules: [ diff --git a/docs/assets/demo/en/export/table-export-format.md b/docs/assets/demo/en/export/table-export-format.md index ecfd11b36..1b3c9aca9 100644 --- a/docs/assets/demo/en/export/table-export-format.md +++ b/docs/assets/demo/en/export/table-export-format.md @@ -24,180 +24,48 @@ By default, when exporting, the text or image inside the exported cell will be o // } from "@visactor/vtable-export"; // When umd is introduced, the export tool will be mounted to VTable.export -const data = [ +const records = [ + { productName: 'aaaa', price: 20, check: { text: 'unchecked', checked: false, disable: false } }, + { productName: 'bbbb', price: 18, check: { text: 'checked', checked: true, disable: false } }, + { productName: 'cccc', price: 16, check: { text: 'disable', checked: true, disable: true } }, + { productName: 'cccc', price: 14, check: { text: 'disable', checked: false, disable: true } }, + { productName: 'eeee', price: 12, check: { text: 'checked', checked: false, disable: false } }, + { productName: 'ffff', price: 10, check: { text: 'checked', checked: false, disable: false } }, + { productName: 'gggg', price: 10, check: { text: 'checked', checked: false, disable: false } } +]; + +const columns = [ { - 类别: '办公用品', - 销售额: '129.696', - 数量: '2', - 利润: '-60.704', - children: [ - { - 类别: '信封', // 对应原子类别 - 销售额: '125.44', - 数量: '2', - 利润: '42.56', - children: [ - { - 类别: '黄色信封', - 销售额: '125.44', - 数量: '2', - 利润: '42.56' - }, - { - 类别: '白色信封', - 销售额: '1375.92', - 数量: '3', - 利润: '550.2' - } - ] - }, - { - 类别: '器具', // 对应原子类别 - 销售额: '1375.92', - 数量: '3', - 利润: '550.2', - children: [ - { - 类别: '订书机', - 销售额: '125.44', - 数量: '2', - 利润: '42.56' - }, - { - 类别: '计算器', - 销售额: '1375.92', - 数量: '3', - 利润: '550.2' - } - ] - } - ] + field: 'isCheck', + title: '', + width: 60, + headerType: 'checkbox', + cellType: 'checkbox' }, { - 类别: '技术', - 销售额: '229.696', - 数量: '20', - 利润: '90.704', - children: [ - { - 类别: '设备', // 对应原子类别 - 销售额: '225.44', - 数量: '5', - 利润: '462.56' - }, - { - 类别: '配件', // 对应原子类别 - 销售额: '375.92', - 数量: '8', - 利润: '550.2' - }, - { - 类别: '复印机', // 对应原子类别 - 销售额: '425.44', - 数量: '7', - 利润: '342.56' - }, - { - 类别: '电话', // 对应原子类别 - 销售额: '175.92', - 数量: '6', - 利润: '750.2' - } - ] + field: 'productName', + title: 'productName', + width: 120 }, { - 类别: '家具', - 销售额: '129.696', - 数量: '2', - 利润: '-60.704', - children: [ - { - 类别: '桌子', // 对应原子类别 - 销售额: '125.44', - 数量: '2', - 利润: '42.56', - children: [ - { - 类别: '黄色桌子', - 销售额: '125.44', - 数量: '2', - 利润: '42.56' - }, - { - 类别: '白色桌子', - 销售额: '1375.92', - 数量: '3', - 利润: '550.2' - } - ] - }, - { - 类别: '椅子', // 对应原子类别 - 销售额: '1375.92', - 数量: '3', - 利润: '550.2', - children: [ - { - 类别: '老板椅', - 销售额: '125.44', - 数量: '2', - 利润: '42.56' - }, - { - 类别: '沙发椅', - 销售额: '1375.92', - 数量: '3', - 利润: '550.2' - } - ] - } - ] + field: 'price', + title: 'checkbox', + width: 120, + cellType: 'checkbox', + disable: true, + checked: true }, { - 类别: '生活家电(懒加载)', - 销售额: '229.696', - 数量: '20', - 利润: '90.704' + field: 'check', + title: 'checkbox', + width: 120, + cellType: 'checkbox' + // disable: true } ]; const option = { - container: document.getElementById(CONTAINER_ID), - columns: [ - { - field: '类别', - tree: true, - title: '类别', - width: 'auto', - sort: true - }, - { - field: '销售额', - title: '销售额', - width: 'auto', - sort: true - // tree: true, - }, - { - field: '利润', - title: '利润', - width: 'auto', - sort: true - } - ], - showPin: true, //显示VTable内置冻结列图标 - widthMode: 'standard', - allowFrozenColCount: 2, - records: data, - - hierarchyIndent: 20, - hierarchyExpandLevel: 2, - hierarchyTextStartAlignment: true, - sortState: { - field: '销售额', - order: 'asc' - }, - theme: VTable.themes.BRIGHT, - defaultRowHeight: 32 + records, + columns }; const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window.tableInstance = tableInstance; @@ -233,7 +101,13 @@ function bindExport() { exportExcelButton.addEventListener('click', async () => { if (window.tableInstance) { - downloadExcel(await exportVTableToExcel(window.tableInstance), 'export'); + downloadExcel(await exportVTableToExcel(window.tableInstance, { + formatExportOutput: ({ cellType, cellValue, table, col, row }) => { + if (cellType === 'checkbox') { + return table.getCellCheckboxState(col, row) ? 'true' : 'false'; + } + } + }), 'export'); } }); } diff --git a/docs/assets/demo/en/export/table-export-ignore-icon.md b/docs/assets/demo/en/export/table-export-ignore-icon.md index 3e0c1a4ed..2640825e1 100644 --- a/docs/assets/demo/en/export/table-export-ignore-icon.md +++ b/docs/assets/demo/en/export/table-export-ignore-icon.md @@ -24,48 +24,180 @@ By default, when the cell has an icon, the icon and text will be treated as an i // } from "@visactor/vtable-export"; // When umd is introduced, the export tool will be mounted to VTable.export -const records = [ - { productName: 'aaaa', price: 20, check: { text: 'unchecked', checked: false, disable: false } }, - { productName: 'bbbb', price: 18, check: { text: 'checked', checked: true, disable: false } }, - { productName: 'cccc', price: 16, check: { text: 'disable', checked: true, disable: true } }, - { productName: 'cccc', price: 14, check: { text: 'disable', checked: false, disable: true } }, - { productName: 'eeee', price: 12, check: { text: 'checked', checked: false, disable: false } }, - { productName: 'ffff', price: 10, check: { text: 'checked', checked: false, disable: false } }, - { productName: 'gggg', price: 10, check: { text: 'checked', checked: false, disable: false } } -]; - -const columns = [ +const data = [ { - field: 'isCheck', - title: '', - width: 60, - headerType: 'checkbox', - cellType: 'checkbox' + 类别: '办公用品', + 销售额: '129.696', + 数量: '2', + 利润: '-60.704', + children: [ + { + 类别: '信封', // 对应原子类别 + 销售额: '125.44', + 数量: '2', + 利润: '42.56', + children: [ + { + 类别: '黄色信封', + 销售额: '125.44', + 数量: '2', + 利润: '42.56' + }, + { + 类别: '白色信封', + 销售额: '1375.92', + 数量: '3', + 利润: '550.2' + } + ] + }, + { + 类别: '器具', // 对应原子类别 + 销售额: '1375.92', + 数量: '3', + 利润: '550.2', + children: [ + { + 类别: '订书机', + 销售额: '125.44', + 数量: '2', + 利润: '42.56' + }, + { + 类别: '计算器', + 销售额: '1375.92', + 数量: '3', + 利润: '550.2' + } + ] + } + ] }, { - field: 'productName', - title: 'productName', - width: 120 + 类别: '技术', + 销售额: '229.696', + 数量: '20', + 利润: '90.704', + children: [ + { + 类别: '设备', // 对应原子类别 + 销售额: '225.44', + 数量: '5', + 利润: '462.56' + }, + { + 类别: '配件', // 对应原子类别 + 销售额: '375.92', + 数量: '8', + 利润: '550.2' + }, + { + 类别: '复印机', // 对应原子类别 + 销售额: '425.44', + 数量: '7', + 利润: '342.56' + }, + { + 类别: '电话', // 对应原子类别 + 销售额: '175.92', + 数量: '6', + 利润: '750.2' + } + ] }, { - field: 'price', - title: 'checkbox', - width: 120, - cellType: 'checkbox', - disable: true, - checked: true + 类别: '家具', + 销售额: '129.696', + 数量: '2', + 利润: '-60.704', + children: [ + { + 类别: '桌子', // 对应原子类别 + 销售额: '125.44', + 数量: '2', + 利润: '42.56', + children: [ + { + 类别: '黄色桌子', + 销售额: '125.44', + 数量: '2', + 利润: '42.56' + }, + { + 类别: '白色桌子', + 销售额: '1375.92', + 数量: '3', + 利润: '550.2' + } + ] + }, + { + 类别: '椅子', // 对应原子类别 + 销售额: '1375.92', + 数量: '3', + 利润: '550.2', + children: [ + { + 类别: '老板椅', + 销售额: '125.44', + 数量: '2', + 利润: '42.56' + }, + { + 类别: '沙发椅', + 销售额: '1375.92', + 数量: '3', + 利润: '550.2' + } + ] + } + ] }, { - field: 'check', - title: 'checkbox', - width: 120, - cellType: 'checkbox' - // disable: true + 类别: '生活家电(懒加载)', + 销售额: '229.696', + 数量: '20', + 利润: '90.704' } ]; const option = { - records, - columns + container: document.getElementById(CONTAINER_ID), + columns: [ + { + field: '类别', + tree: true, + title: '类别', + width: 'auto', + sort: true + }, + { + field: '销售额', + title: '销售额', + width: 'auto', + sort: true + // tree: true, + }, + { + field: '利润', + title: '利润', + width: 'auto', + sort: true + } + ], + showPin: true, //显示VTable内置冻结列图标 + widthMode: 'standard', + allowFrozenColCount: 2, + records: data, + + hierarchyIndent: 20, + hierarchyExpandLevel: 2, + hierarchyTextStartAlignment: true, + sortState: { + field: '销售额', + order: 'asc' + }, + theme: VTable.themes.BRIGHT, + defaultRowHeight: 32 }; const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window.tableInstance = tableInstance; @@ -101,7 +233,9 @@ function bindExport() { exportExcelButton.addEventListener('click', async () => { if (window.tableInstance) { - downloadExcel(await exportVTableToExcel(window.tableInstance), 'export'); + downloadExcel(await exportVTableToExcel(window.tableInstance, { + ignoreIcon: true + }), 'export'); } }); } diff --git a/docs/assets/demo/menu.json b/docs/assets/demo/menu.json index 4238264d4..f75067a33 100644 --- a/docs/assets/demo/menu.json +++ b/docs/assets/demo/menu.json @@ -325,6 +325,13 @@ "en": "Chart Type In PivotTable" } }, + { + "path": "pivot-sparkline", + "title": { + "zh": "透视表迷你图展示", + "en": "Sparkline In PivotTable" + } + }, { "path": "composite-cellType", "title": { @@ -485,6 +492,13 @@ "zh": "分页(翻页)", "en": "pagination" } + }, + { + "path": "pivot-table-corner-title", + "title": { + "zh": "透视表角头显示维度名", + "en": "Pivot Table corner title" + } } ] }, diff --git a/docs/assets/demo/zh/basic-functionality/pivot-table-corner-title.md b/docs/assets/demo/zh/basic-functionality/pivot-table-corner-title.md new file mode 100644 index 000000000..745b79bec --- /dev/null +++ b/docs/assets/demo/zh/basic-functionality/pivot-table-corner-title.md @@ -0,0 +1,171 @@ +--- +category: examples +group: Basic Features +title: 透视表角头显示维度名称 +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-table-corner-title.png +link: '../guide/table_type/Pivot_table/pivot_table_useage' +option: PivotTable#corner +--- + +# 透视表角头显示维度名称 + +将角头标题显示内容依据设置为`'all'`,则角头单元格内容为行维度名称和列维度名称的拼接。 + +titleOnDimension 角头标题显示内容依据: + +- 'column' 列维度名称作为角头单元格内容 +- 'row' 行维度名称作为角头单元格内容 +- 'none' 角头单元格内容为空 +- 'all' 角头单元格内容为行维度名称和列维度名称的拼接 + +## 关键配置 + +- `PivotTable` +- `columns` +- `rows` +- `indicators` +- `corner.titleOnDimension` 角头标题显示内容依据 + +## 代码演示 + +```javascript livedemo template=vtable +let tableInstance; +fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_Pivot_Chart_data.json') + .then(res => res.json()) + .then(data => { + const option = { + records: data, + rows: [ + { + dimensionKey: 'Category', + title: 'Category', + headerStyle: { + textStick: true, + bgColor(arg) { + if (arg.dataValue === 'Row Totals') { + return '#ff9900'; + } + return '#ECF1F5'; + } + }, + width: 'auto' + }, + { + dimensionKey: 'Sub-Category', + title: 'Sub-Catogery', + headerStyle: { + textStick: true + }, + width: 'auto' + } + ], + columns: [ + { + dimensionKey: 'Region', + title: 'Region', + headerStyle: { + textStick: true + }, + width: 'auto' + }, + { + dimensionKey: 'Segment', + title: 'Segment', + headerStyle: { + textStick: true + }, + width: 'auto' + } + ], + indicators: [ + { + indicatorKey: 'Quantity', + title: 'Quantity', + width: 'auto', + sort: true, + headerStyle: { + fontWeight: 'normal' + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return 'black'; + return 'red'; + }, + bgColor(arg) { + const rowHeaderPaths = arg.cellHeaderPaths.rowHeaderPaths; + if (rowHeaderPaths?.[1]?.value === 'Sub Totals') { + return '#ba54ba'; + } else if (rowHeaderPaths?.[0]?.value === 'Row Totals') { + return '#ff9900'; + } + return undefined; + } + } + }, + { + indicatorKey: 'Sales', + title: 'Sales', + width: 'auto', + sort: true, + headerStyle: { + fontWeight: 'normal' + }, + format: rec => { + return '$' + Number(rec).toFixed(2); + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return 'black'; + return 'red'; + }, + bgColor(arg) { + const rowHeaderPaths = arg.cellHeaderPaths.rowHeaderPaths; + if (rowHeaderPaths?.[1]?.value === 'Sub Totals') { + return '#ba54ba'; + } else if (rowHeaderPaths?.[0]?.value === 'Row Totals') { + return '#ff9900'; + } + return undefined; + } + } + }, + { + indicatorKey: 'Profit', + title: 'Profit', + width: 'auto', + showSort: false, + headerStyle: { + fontWeight: 'normal' + }, + format: rec => { + return '$' + Number(rec).toFixed(2); + }, + style: { + padding: [16, 28, 16, 28], + color(args) { + if (args.dataValue >= 0) return 'black'; + return 'red'; + }, + bgColor(arg) { + const rowHeaderPaths = arg.cellHeaderPaths.rowHeaderPaths; + if (rowHeaderPaths?.[1]?.value === 'Sub Totals') { + return '#ba54ba'; + } else if (rowHeaderPaths?.[0]?.value === 'Row Totals') { + return '#ff9900'; + } + return undefined; + } + } + } + ], + corner: { + titleOnDimension: 'all' + }, + widthMode: 'standard' + }; + tableInstance = new VTable.PivotTable(document.getElementById(CONTAINER_ID), option); + window['tableInstance'] = tableInstance; + }); +``` diff --git a/docs/assets/demo/zh/cell-type/pivot-sparkline.md b/docs/assets/demo/zh/cell-type/pivot-sparkline.md new file mode 100644 index 000000000..c3e8d9861 --- /dev/null +++ b/docs/assets/demo/zh/cell-type/pivot-sparkline.md @@ -0,0 +1,133 @@ +--- +category: examples +group: Cell Type +title: 透视表展示迷你图 +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-sparkline.png +link: '../guide/cell_type/chart' +option: PivotTable-indicators-chart#cellType +--- + +# 透视表展示迷你图 + +将单元格对应的数据以迷你图表的形式展示。 + +## 关键配置 + +- `cellType: 'sparkline'` 指定类型 chart +- `sparklineSpec: {}` 迷你图 spec +- `dataConfig.aggregationRules` 配置聚合规则 这里用到的规则是`RECORD`类型,表示需要搜集单元格对一个的源数据记录,作为迷你图表的数据源 + +## 代码演示 + +```javascript livedemo template=vtable +VTable.register.chartModule('vchart', VChart); +let tableInstance; +fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_data.json') + .then(res => res.json()) + .then(data => { + const columns = [ + { + dimensionKey: 'Region', + title: 'Region', + headerStyle: { + textStick: true + } + } + ]; + const rows = ['Category']; + const indicators = [ + { + indicatorKey: 'Sales', + title: 'Sales', + width: 120, + format: rec => { + return '$' + Number(rec).toFixed(2); + } + }, + { + indicatorKey: 'SalesRecords', + title: 'Sales Trend', + cellType: 'sparkline', + width: 500, + sparklineSpec: { + type: 'line', + xField: 'Order Date', + yField: 'Sales', + pointShowRule: 'none', + smooth: true, + line: { + style: { + stroke: '#2E62F1', + strokeWidth: 2 + // interpolate: 'monotone', + } + }, + point: { + hover: { + stroke: 'blue', + strokeWidth: 1, + fill: 'red', + shape: 'circle', + size: 4 + }, + style: { + stroke: 'red', + strokeWidth: 1, + fill: 'yellow', + shape: 'circle', + size: 2 + } + }, + crosshair: { + style: { + stroke: 'gray', + strokeWidth: 1 + } + } + } + } + ]; + const option = { + dataConfig: { + aggregationRules: [ + //做聚合计算的依据,如销售额如果没有配置则默认按聚合sum计算结果显示单元格内容 + { + indicatorKey: 'SalesRecords', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.RECORD //计算类型 + } + ] + }, + rows, + columns, + indicators, + indicatorsAsCol: true, + records: data, + defaultRowHeight: 80, + defaultHeaderRowHeight: 50, + defaultColWidth: 280, + defaultHeaderColWidth: 130, + indicatorTitle: '指标', + autoWrapText: true, + // widthMode:'adaptive', + // heightMode:'adaptive', + corner: { + titleOnDimension: 'row', + headerStyle: { + autoWrapText: true + } + } + }; + + tableInstance = new VTable.PivotTable(document.getElementById(CONTAINER_ID), option); + const { LEGEND_ITEM_CLICK } = VTable.ListTable.EVENT_TYPE; + window.tableInstance = tableInstance; + tableInstance.onVChartEvent('click', args => { + console.log('onVChartEvent click', args); + }); + tableInstance.onVChartEvent('mouseover', args => { + console.log('onVChartEvent mouseover', args); + }); + window.tableInstance = tableInstance; + }); +``` diff --git a/docs/assets/demo/zh/data-analysis/pivot-analysis-sort-indicator.md b/docs/assets/demo/zh/data-analysis/pivot-analysis-sort-indicator.md index a541386a3..cf750fa36 100644 --- a/docs/assets/demo/zh/data-analysis/pivot-analysis-sort-indicator.md +++ b/docs/assets/demo/zh/data-analysis/pivot-analysis-sort-indicator.md @@ -154,10 +154,7 @@ fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American } ], corner: { - titleOnDimension: 'row', - headerStyle: { - textStick: true - } + titleOnDimension: 'row' }, dataConfig: { sortRules: [ diff --git a/docs/assets/demo/zh/export/table-export-format.md b/docs/assets/demo/zh/export/table-export-format.md index 27806c3a1..c15733142 100644 --- a/docs/assets/demo/zh/export/table-export-format.md +++ b/docs/assets/demo/zh/export/table-export-format.md @@ -24,180 +24,48 @@ link: '../../guide/export/excel' // } from "@visactor/vtable-export"; // umd引入时导出工具会挂载到VTable.export -const data = [ +const records = [ + { productName: 'aaaa', price: 20, check: { text: 'unchecked', checked: false, disable: false } }, + { productName: 'bbbb', price: 18, check: { text: 'checked', checked: true, disable: false } }, + { productName: 'cccc', price: 16, check: { text: 'disable', checked: true, disable: true } }, + { productName: 'cccc', price: 14, check: { text: 'disable', checked: false, disable: true } }, + { productName: 'eeee', price: 12, check: { text: 'checked', checked: false, disable: false } }, + { productName: 'ffff', price: 10, check: { text: 'checked', checked: false, disable: false } }, + { productName: 'gggg', price: 10, check: { text: 'checked', checked: false, disable: false } } +]; + +const columns = [ { - 类别: '办公用品', - 销售额: '129.696', - 数量: '2', - 利润: '-60.704', - children: [ - { - 类别: '信封', // 对应原子类别 - 销售额: '125.44', - 数量: '2', - 利润: '42.56', - children: [ - { - 类别: '黄色信封', - 销售额: '125.44', - 数量: '2', - 利润: '42.56' - }, - { - 类别: '白色信封', - 销售额: '1375.92', - 数量: '3', - 利润: '550.2' - } - ] - }, - { - 类别: '器具', // 对应原子类别 - 销售额: '1375.92', - 数量: '3', - 利润: '550.2', - children: [ - { - 类别: '订书机', - 销售额: '125.44', - 数量: '2', - 利润: '42.56' - }, - { - 类别: '计算器', - 销售额: '1375.92', - 数量: '3', - 利润: '550.2' - } - ] - } - ] + field: 'isCheck', + title: '', + width: 60, + headerType: 'checkbox', + cellType: 'checkbox' }, { - 类别: '技术', - 销售额: '229.696', - 数量: '20', - 利润: '90.704', - children: [ - { - 类别: '设备', // 对应原子类别 - 销售额: '225.44', - 数量: '5', - 利润: '462.56' - }, - { - 类别: '配件', // 对应原子类别 - 销售额: '375.92', - 数量: '8', - 利润: '550.2' - }, - { - 类别: '复印机', // 对应原子类别 - 销售额: '425.44', - 数量: '7', - 利润: '342.56' - }, - { - 类别: '电话', // 对应原子类别 - 销售额: '175.92', - 数量: '6', - 利润: '750.2' - } - ] + field: 'productName', + title: 'productName', + width: 120 }, { - 类别: '家具', - 销售额: '129.696', - 数量: '2', - 利润: '-60.704', - children: [ - { - 类别: '桌子', // 对应原子类别 - 销售额: '125.44', - 数量: '2', - 利润: '42.56', - children: [ - { - 类别: '黄色桌子', - 销售额: '125.44', - 数量: '2', - 利润: '42.56' - }, - { - 类别: '白色桌子', - 销售额: '1375.92', - 数量: '3', - 利润: '550.2' - } - ] - }, - { - 类别: '椅子', // 对应原子类别 - 销售额: '1375.92', - 数量: '3', - 利润: '550.2', - children: [ - { - 类别: '老板椅', - 销售额: '125.44', - 数量: '2', - 利润: '42.56' - }, - { - 类别: '沙发椅', - 销售额: '1375.92', - 数量: '3', - 利润: '550.2' - } - ] - } - ] + field: 'price', + title: 'checkbox', + width: 120, + cellType: 'checkbox', + disable: true, + checked: true }, { - 类别: '生活家电(懒加载)', - 销售额: '229.696', - 数量: '20', - 利润: '90.704' + field: 'check', + title: 'checkbox', + width: 120, + cellType: 'checkbox' + // disable: true } ]; const option = { - container: document.getElementById(CONTAINER_ID), - columns: [ - { - field: '类别', - tree: true, - title: '类别', - width: 'auto', - sort: true - }, - { - field: '销售额', - title: '销售额', - width: 'auto', - sort: true - // tree: true, - }, - { - field: '利润', - title: '利润', - width: 'auto', - sort: true - } - ], - showPin: true, //显示VTable内置冻结列图标 - widthMode: 'standard', - allowFrozenColCount: 2, - records: data, - - hierarchyIndent: 20, - hierarchyExpandLevel: 2, - hierarchyTextStartAlignment: true, - sortState: { - field: '销售额', - order: 'asc' - }, - theme: VTable.themes.BRIGHT, - defaultRowHeight: 32 + records, + columns }; const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window.tableInstance = tableInstance; @@ -233,7 +101,13 @@ function bindExport() { exportExcelButton.addEventListener('click', async () => { if (window.tableInstance) { - downloadExcel(await exportVTableToExcel(window.tableInstance), 'export'); + downloadExcel(await exportVTableToExcel(window.tableInstance, { + formatExportOutput: ({ cellType, cellValue, table, col, row }) => { + if (cellType === 'checkbox') { + return table.getCellCheckboxState(col, row) ? 'true' : 'false'; + } + } + }), 'export'); } }); } diff --git a/docs/assets/demo/zh/export/table-export-ignore-icon.md b/docs/assets/demo/zh/export/table-export-ignore-icon.md index a435862a4..4f92b790b 100644 --- a/docs/assets/demo/zh/export/table-export-ignore-icon.md +++ b/docs/assets/demo/zh/export/table-export-ignore-icon.md @@ -24,48 +24,180 @@ link: '../../guide/export/excel' // } from "@visactor/vtable-export"; // umd引入时导出工具会挂载到VTable.export -const records = [ - { productName: 'aaaa', price: 20, check: { text: 'unchecked', checked: false, disable: false } }, - { productName: 'bbbb', price: 18, check: { text: 'checked', checked: true, disable: false } }, - { productName: 'cccc', price: 16, check: { text: 'disable', checked: true, disable: true } }, - { productName: 'cccc', price: 14, check: { text: 'disable', checked: false, disable: true } }, - { productName: 'eeee', price: 12, check: { text: 'checked', checked: false, disable: false } }, - { productName: 'ffff', price: 10, check: { text: 'checked', checked: false, disable: false } }, - { productName: 'gggg', price: 10, check: { text: 'checked', checked: false, disable: false } } -]; - -const columns = [ +const data = [ { - field: 'isCheck', - title: '', - width: 60, - headerType: 'checkbox', - cellType: 'checkbox' + 类别: '办公用品', + 销售额: '129.696', + 数量: '2', + 利润: '-60.704', + children: [ + { + 类别: '信封', // 对应原子类别 + 销售额: '125.44', + 数量: '2', + 利润: '42.56', + children: [ + { + 类别: '黄色信封', + 销售额: '125.44', + 数量: '2', + 利润: '42.56' + }, + { + 类别: '白色信封', + 销售额: '1375.92', + 数量: '3', + 利润: '550.2' + } + ] + }, + { + 类别: '器具', // 对应原子类别 + 销售额: '1375.92', + 数量: '3', + 利润: '550.2', + children: [ + { + 类别: '订书机', + 销售额: '125.44', + 数量: '2', + 利润: '42.56' + }, + { + 类别: '计算器', + 销售额: '1375.92', + 数量: '3', + 利润: '550.2' + } + ] + } + ] }, { - field: 'productName', - title: 'productName', - width: 120 + 类别: '技术', + 销售额: '229.696', + 数量: '20', + 利润: '90.704', + children: [ + { + 类别: '设备', // 对应原子类别 + 销售额: '225.44', + 数量: '5', + 利润: '462.56' + }, + { + 类别: '配件', // 对应原子类别 + 销售额: '375.92', + 数量: '8', + 利润: '550.2' + }, + { + 类别: '复印机', // 对应原子类别 + 销售额: '425.44', + 数量: '7', + 利润: '342.56' + }, + { + 类别: '电话', // 对应原子类别 + 销售额: '175.92', + 数量: '6', + 利润: '750.2' + } + ] }, { - field: 'price', - title: 'checkbox', - width: 120, - cellType: 'checkbox', - disable: true, - checked: true + 类别: '家具', + 销售额: '129.696', + 数量: '2', + 利润: '-60.704', + children: [ + { + 类别: '桌子', // 对应原子类别 + 销售额: '125.44', + 数量: '2', + 利润: '42.56', + children: [ + { + 类别: '黄色桌子', + 销售额: '125.44', + 数量: '2', + 利润: '42.56' + }, + { + 类别: '白色桌子', + 销售额: '1375.92', + 数量: '3', + 利润: '550.2' + } + ] + }, + { + 类别: '椅子', // 对应原子类别 + 销售额: '1375.92', + 数量: '3', + 利润: '550.2', + children: [ + { + 类别: '老板椅', + 销售额: '125.44', + 数量: '2', + 利润: '42.56' + }, + { + 类别: '沙发椅', + 销售额: '1375.92', + 数量: '3', + 利润: '550.2' + } + ] + } + ] }, { - field: 'check', - title: 'checkbox', - width: 120, - cellType: 'checkbox' - // disable: true + 类别: '生活家电(懒加载)', + 销售额: '229.696', + 数量: '20', + 利润: '90.704' } ]; const option = { - records, - columns + container: document.getElementById(CONTAINER_ID), + columns: [ + { + field: '类别', + tree: true, + title: '类别', + width: 'auto', + sort: true + }, + { + field: '销售额', + title: '销售额', + width: 'auto', + sort: true + // tree: true, + }, + { + field: '利润', + title: '利润', + width: 'auto', + sort: true + } + ], + showPin: true, //显示VTable内置冻结列图标 + widthMode: 'standard', + allowFrozenColCount: 2, + records: data, + + hierarchyIndent: 20, + hierarchyExpandLevel: 2, + hierarchyTextStartAlignment: true, + sortState: { + field: '销售额', + order: 'asc' + }, + theme: VTable.themes.BRIGHT, + defaultRowHeight: 32 }; const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window.tableInstance = tableInstance; @@ -101,7 +233,9 @@ function bindExport() { exportExcelButton.addEventListener('click', async () => { if (window.tableInstance) { - downloadExcel(await exportVTableToExcel(window.tableInstance), 'export'); + downloadExcel(await exportVTableToExcel(window.tableInstance, { + ignoreIcon: true + }), 'export'); } }); } diff --git a/docs/assets/guide/en/Contribution_Guide.md b/docs/assets/guide/en/Contribution_Guide.md index 3514f410a..75d475825 100644 --- a/docs/assets/guide/en/Contribution_Guide.md +++ b/docs/assets/guide/en/Contribution_Guide.md @@ -151,6 +151,16 @@ Fill in the changes of this submission according to the template: After filling in the relevant information, click Create pull request to submit. +### Other points to note + +1. Unit Testing: + +If it involves interface modification, please add unit tests in the **tests** directory and run the `rushx test` command for testing. + +When pushing the code, the unit test will be automatically checked before it is pushed. If all pass, the code will be pushed to the remote. If it fails, there should be a problem with the code logic or the unit test data needs to be modified. Please correct it according to the situation. + +If you encounter the error "Cannot find module '@visactor/vtable-editors'" when running unit tests, please execute the `rushx build` command in the vtable-editors project first, and then return to the vtable project to execute the command. + ## Mini Task Development Guide "**good first issue**" is a common label in open source communities, and its purpose is to help new contributors find suitable entry-level issues. diff --git a/docs/assets/guide/en/data_analysis/pivot_table_dataAnalysis.md b/docs/assets/guide/en/data_analysis/pivot_table_dataAnalysis.md index 2b07f0aef..66b7ee0cb 100644 --- a/docs/assets/guide/en/data_analysis/pivot_table_dataAnalysis.md +++ b/docs/assets/guide/en/data_analysis/pivot_table_dataAnalysis.md @@ -188,6 +188,11 @@ Configuration example: indicatorKey: 'OrderSalesValue', //Indicator name field: 'Sales', //Indicator based on field aggregationType: VTable.TYPES.AggregationType.NONE, //don't aggregate + }, + { + indicatorKey: 'orderRecords', //Indicator name + field: 'Sales', //Indicator based on field + aggregationType: VTable.TYPES.AggregationType.RECORD, //don't aggregate. Match all the corresponding data as the value of the cell } ] ``` @@ -219,6 +224,8 @@ dataConfig:{ The sales indicator in this record is a non-numeric value, and it is required to display `"NULL"` directly in the table cell. In this case, you can set `NONE` to require the internal aggregation logic of VTable to directly obtain the value of the sales field without aggregation. +2. The usage scenario of AggregationType.RECORD indicator without aggregation is mainly used to match all data according to the data record passed by the user, and use it as the display data of the cell. The usage scenario is such as collecting data sets as mini-charts. For specific demo, see: https://visactor.io/vtable/demo/cell-type/pivot-sparkline + ### 5. Derive Field [option description](../../option/PivotTable#dataConfig.derivedFieldRules) diff --git a/docs/assets/guide/en/export/excel.md b/docs/assets/guide/en/export/excel.md index 5f0135e27..b5f01addf 100644 --- a/docs/assets/guide/en/export/excel.md +++ b/docs/assets/guide/en/export/excel.md @@ -76,4 +76,36 @@ const excelOption = { } }; downloadExcel(await exportVTableToExcel(tableInstance, excelOption)); -``` \ No newline at end of file +``` + + +### formatExcelJSCell + +If you need to further customize the export style, you can set `formatExcelJSCell` to a function. The function parameters are cell information and ExcelJS cell objects. The function return value is the ExcelJS cell object. If `undefined` is returned, the default export logic is used. You can automatically set ExcelJS cell properties in the function. For details, please refer to https://github.com/exceljs/exceljs?tab=readme-ov-file#styles + +```ts +type CellInfo = { + cellType: string; + cellValue: string; + table: IVTable; + col: number; + row: number; +}; + +type ExportVTableToExcelOptions = { + // ...... + formatExceljsCell?: (cellInfo: CellInfo, cellInExceljs: ExcelJS.Cell) => ExcelJS.Cell; +}; +``` + +```js +const excelOption = { + formatExcelJSCell: (cellInfo, cell) => { + if (cellInfo.col === 1) { + cell.numFmt = '0.00%'; + } + return cell; + } +}; +downloadExcel(await exportVTableToExcel(tableInstance, excelOption)); +``` diff --git a/docs/assets/guide/zh/Contribution_Guide.md b/docs/assets/guide/zh/Contribution_Guide.md index 56d3eda45..d95155c93 100644 --- a/docs/assets/guide/zh/Contribution_Guide.md +++ b/docs/assets/guide/zh/Contribution_Guide.md @@ -149,6 +149,16 @@ git push origin docs/add-funnel-demo 相关信息填写完成后,点击 Create pull request 提交。 +### 其他注意点 + +1. 单元测试: + +如果是涉及接口修改请在**tests**目录下添加单元测试,并运行`rushx test`命令进行测试。 + +当 push 推送代码前会自动进行单元测试的检查,如果全部通过会推送代码到远程,如果没有通过那么应该是代码逻辑有问题或者需求修改单测数据,请根据情况进行修正。 + +运行单元测试中如果遇到"Cannot find module '@visactor/vtable-editors'" 的错误,请先到 vtable-editors 项目中执行`rushx build`命令,然后再回到 vtable 项目中执行命令。 + ## Mini Task 开发指南 "**good first issue**" 是一个在开源社区常见的标签,这个标签的目的是帮助新贡献者找到适合入门的问题。 diff --git a/docs/assets/guide/zh/data_analysis/pivot_table_dataAnalysis.md b/docs/assets/guide/zh/data_analysis/pivot_table_dataAnalysis.md index ba23260e6..f7bb70e1b 100644 --- a/docs/assets/guide/zh/data_analysis/pivot_table_dataAnalysis.md +++ b/docs/assets/guide/zh/data_analysis/pivot_table_dataAnalysis.md @@ -188,6 +188,11 @@ filterRules: [ indicatorKey: 'OrderSalesValue', //指标名称 field: 'Sales', //指标依据字段 aggregationType: VTable.TYPES.AggregationType.NONE, //不做聚合 匹配到其中对应数据获取其对应field的值 + }, + { + indicatorKey: 'orderRecords', //指标名称 + field: 'Sales', //指标依据字段 + aggregationType: VTable.TYPES.AggregationType.RECORD, //不做聚合 匹配到其中对应的全部数据作为单元格的值 } ] ``` @@ -220,6 +225,8 @@ dataConfig:{ 其中该条 record 中 sales 指标是个非数值型的值,而且需求要将`"NULL"`直接显示到表格单元格中,那么可以设置 NONE,要求 VTable 的内部聚合逻辑不聚合直接取`sales`字段值。 +2. AggregationType.RECORD 指标不做聚合的使用场景主要用于根据用户传入数据 record 匹配到所有数据,将其作为单元格的展示数据,用法场景如需要搜集数据集作为迷你图展示,具体 demo 见:https://visactor.io/vtable/demo/cell-type/pivot-sparkline + ### 5. 派生字段 [option 说明](../../option/PivotTable#dataConfig.derivedFieldRules) diff --git a/docs/assets/guide/zh/export/excel.md b/docs/assets/guide/zh/export/excel.md index 52c5b1e69..e8b67640b 100644 --- a/docs/assets/guide/zh/export/excel.md +++ b/docs/assets/guide/zh/export/excel.md @@ -62,7 +62,7 @@ type CellInfo = { }; type ExportVTableToExcelOptions = { - ignoreIcon?: boolean; + // ...... formatExportOutput?: (cellInfo: CellInfo) => string | undefined; }; ``` @@ -76,4 +76,35 @@ const excelOption = { } }; downloadExcel(await exportVTableToExcel(tableInstance, excelOption)); +``` + +### formatExcelJSCell + +对于导出样式有进一步的定制化需求的话,可以设置`formatExcelJSCell`为一个函数,函数的参数为单元格信息和ExcelJS的单元格对象,函数的返回值为ExcelJS的单元格对象,如果返回`undefined`,则按照默认导出逻辑处理。可以在函数中自动设置ExcelJS的单元格属性,具体可以参考 https://github.com/exceljs/exceljs?tab=readme-ov-file#styles + +```ts +type CellInfo = { + cellType: string; + cellValue: string; + table: IVTable; + col: number; + row: number; +}; + +type ExportVTableToExcelOptions = { + // ...... + formatExceljsCell?: (cellInfo: CellInfo, cellInExceljs: ExcelJS.Cell) => ExcelJS.Cell; +}; +``` + +```js +const excelOption = { + formatExcelJSCell: (cellInfo, cell) => { + if (cellInfo.col === 1) { + cell.numFmt = '0.00%'; + } + return cell; + } +}; +downloadExcel(await exportVTableToExcel(tableInstance, excelOption)); ``` \ No newline at end of file diff --git a/docs/assets/option/en/column/base-column-type.md b/docs/assets/option/en/column/base-column-type.md index e6ef00ebf..cfb81910b 100644 --- a/docs/assets/option/en/column/base-column-type.md +++ b/docs/assets/option/en/column/base-column-type.md @@ -251,4 +251,9 @@ Data aggregation summary 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 +Please refer to [the tutorial document](https://visactor.io/vtable/guide/data_analysis/list_table_dataAnalysis) + +${prefix} hide(boolean) = false +Not required. + +Weather hide column. \ No newline at end of file diff --git a/docs/assets/option/en/common/pivot-corner.md b/docs/assets/option/en/common/pivot-corner.md index 5d4fc075e..f1945b492 100644 --- a/docs/assets/option/en/common/pivot-corner.md +++ b/docs/assets/option/en/common/pivot-corner.md @@ -3,9 +3,11 @@ ${prefix} titleOnDimension(string) ='row' Corner header content display based on: + - 'column' The column dimension name is used as the corner header cell content - 'row' The row dimension name is used as the corner header cell content - 'none' The corner header cell content is empty +- 'all' means the header cell content is the concatenation of the row dimension name and the column dimension name ${prefix} headerType(string) @@ -17,4 +19,4 @@ Header cell style, the configuration items vary slightly depending on the header - For headerType 'text', refer to [headerStyle](../option/PivotTable-columns-text#headerStyle.bgColor) - For headerType 'link', refer to [headerStyle](../option/PivotTable-columns-link#headerStyle.bgColor) -- For headerType 'image', refer to [headerStyle](../option/PivotTable-columns-image#headerStyle.bgColor) \ No newline at end of file +- For headerType 'image', refer to [headerStyle](../option/PivotTable-columns-image#headerStyle.bgColor) diff --git a/docs/assets/option/zh/column/base-column-type.md b/docs/assets/option/zh/column/base-column-type.md index c00a89820..b018fbe95 100644 --- a/docs/assets/option/zh/column/base-column-type.md +++ b/docs/assets/option/zh/column/base-column-type.md @@ -253,4 +253,10 @@ ${prefix} aggregation(Aggregation | CustomAggregation | Array) 全局 option 也可以配置,对每一列都配置聚合规则。 -可参考教程文档 +可参考[教程文档](https://visactor.io/vtable/guide/data_analysis/list_table_dataAnalysis) + + +${prefix} hide(boolean) = false +非必填。 + +是否隐藏列 \ No newline at end of file diff --git a/docs/assets/option/zh/common/pivot-corner.md b/docs/assets/option/zh/common/pivot-corner.md index 15629c2a1..c93492f5d 100644 --- a/docs/assets/option/zh/common/pivot-corner.md +++ b/docs/assets/option/zh/common/pivot-corner.md @@ -1,12 +1,13 @@ - {{ target: pivot-corner-define }} ${prefix} titleOnDimension(string) ='row' 角头标题显示内容依据: + - 'column' 列维度名称作为角头单元格内容 - 'row' 行维度名称作为角头单元格内容 - 'none' 角头单元格内容为空 +- 'all' 角头单元格内容为行维度名称和列维度名称的拼接 ${prefix} headerType(string) @@ -14,8 +15,8 @@ ${prefix} headerType(string) ${prefix} headerStyle(TODO) -表头单元格样式,配置项根据headerType不同有略微差别。每种headerStyle的配置项可参考: +表头单元格样式,配置项根据 headerType 不同有略微差别。每种 headerStyle 的配置项可参考: -- headerType为'text',对应[headerStyle](../option/PivotTable-columns-text#headerStyle.bgColor) -- headerType为'link',对应[headerStyle](../option/PivotTable-columns-link#headerStyle.bgColor) -- headerType为'image',对应[headerStyle](../option/PivotTable-columns-image#headerStyle.bgColor) \ No newline at end of file +- headerType 为'text',对应[headerStyle](../option/PivotTable-columns-text#headerStyle.bgColor) +- headerType 为'link',对应[headerStyle](../option/PivotTable-columns-link#headerStyle.bgColor) +- headerType 为'image',对应[headerStyle](../option/PivotTable-columns-image#headerStyle.bgColor) diff --git a/docs/package.json b/docs/package.json index 3dcb57689..072c7bfcc 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,6 +5,7 @@ "private": true, "scripts": { "serve": "vite --host", + "build": "vite build && cp -r ./assets ./dist", "watch": "node ./libs/template-parse/build.js --env dev --watch", "start": "npx concurrently --kill-others \"npm:watch\" \"npm:serve\"" }, @@ -25,7 +26,8 @@ "react-dom": "^18.0.0", "react-router-dom": "6.9.0", "lodash": "4.17.21", - "openinula": "~0.1.2-SNAPSHOT" + "openinula": "~0.1.2-SNAPSHOT", + "@visactor/vutils": "~0.18.9" }, "devDependencies": { "@internal/eslint-config": "workspace:*", diff --git a/packages/openinula-vtable/package.json b/packages/openinula-vtable/package.json index 476032a2c..f5df8e134 100644 --- a/packages/openinula-vtable/package.json +++ b/packages/openinula-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/openinula-vtable", - "version": "1.4.1", + "version": "1.4.2", "description": "The openinula version of VTable", "keywords": [ "openinula", diff --git a/packages/react-vtable/package.json b/packages/react-vtable/package.json index 693e7f560..bb5fd4634 100644 --- a/packages/react-vtable/package.json +++ b/packages/react-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vtable", - "version": "1.4.1", + "version": "1.4.2", "description": "The react version of VTable", "keywords": [ "react", diff --git a/packages/vtable-editors/package.json b/packages/vtable-editors/package.json index a8b34ba1e..0fbedcc65 100644 --- a/packages/vtable-editors/package.json +++ b/packages/vtable-editors/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-editors", - "version": "1.4.1", + "version": "1.4.2", "description": "", "sideEffects": false, "main": "cjs/index.js", diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index 7b7aab550..6ce572554 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-export", - "version": "1.4.1", + "version": "1.4.2", "description": "The export util of VTable", "author": { "name": "VisActor", diff --git a/packages/vtable-export/src/excel/index.ts b/packages/vtable-export/src/excel/index.ts index 371022296..e47e48348 100644 --- a/packages/vtable-export/src/excel/index.ts +++ b/packages/vtable-export/src/excel/index.ts @@ -18,6 +18,7 @@ export type CellInfo = { export type ExportVTableToExcelOptions = { ignoreIcon?: boolean; formatExportOutput?: (cellInfo: CellInfo) => string | undefined; + formatExcelJSCell?: (cellInfo: CellInfo, cellInExcelJS: ExcelJS.Cell) => ExcelJS.Cell; }; export async function exportVTableToExcel(tableInstance: IVTable, options?: ExportVTableToExcelOptions) { @@ -132,17 +133,25 @@ function addCell( const cellInfo = { cellType, cellValue, table: tableInstance, col, row }; const formattedValue = options.formatExportOutput(cellInfo); if (formattedValue !== undefined) { - const cell = worksheet.getCell(encodeCellAddress(col, row)); + let cell = worksheet.getCell(encodeCellAddress(col, row)); cell.value = formattedValue; cell.font = getCellFont(cellStyle, cellType); cell.fill = getCellFill(cellStyle); cell.border = getCellBorder(cellStyle); const offset = getHierarchyOffset(col, row, tableInstance as any); cell.alignment = getCellAlignment(cellStyle, Math.ceil(offset / cell.font.size)); - return; + + if (cell && options.formatExcelJSCell) { + const formatedCell = options.formatExcelJSCell({ cellType, cellValue, table: tableInstance, col, row }, cell); + if (formatedCell) { + cell = formatedCell; + } + } + return cell; } } + let cell; if ( cellType === 'image' || cellType === 'video' || @@ -165,7 +174,7 @@ function addCell( // ext: { width: tableInstance.getColWidth(col), height: tableInstance.getRowHeight(row) } }); } else if (cellType === 'text' || cellType === 'link') { - const cell = worksheet.getCell(encodeCellAddress(col, row)); + cell = worksheet.getCell(encodeCellAddress(col, row)); cell.value = getCellValue(cellValue, cellType); cell.font = getCellFont(cellStyle, cellType); cell.fill = getCellFill(cellStyle); @@ -188,6 +197,14 @@ function addCell( }); tableInstance.scenegraph.updateNextFrame(); // rerender chart to avoid display error } + + if (cell && options.formatExcelJSCell) { + const formatedCell = options.formatExcelJSCell({ cellType, cellValue, table: tableInstance, col, row }, cell); + if (formatedCell) { + cell = formatedCell; + } + } + return cell; } function getCellValue(cellValue: string, cellType: CellType) { diff --git a/packages/vtable-search/package.json b/packages/vtable-search/package.json index 50b2b6f54..72f4d188b 100644 --- a/packages/vtable-search/package.json +++ b/packages/vtable-search/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-search", - "version": "1.4.1", + "version": "1.4.2", "description": "The search util of VTable", "author": { "name": "VisActor", diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index 3a18da56d..f024bba78 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -1,6 +1,51 @@ { "name": "@visactor/vtable", "entries": [ + { + "version": "1.4.2", + "tag": "@visactor/vtable_v1.4.2", + "date": "Tue, 02 Jul 2024 12:48:08 GMT", + "comments": { + "none": [ + { + "comment": "feat: corner title can display row and column diemensionTitle #1926\n\n" + }, + { + "comment": "fix: when not exit edit state then can not select other cells #1974\n\n" + }, + { + "comment": "fix: selected_clear event trigger #1981\n\n" + }, + { + "comment": "feat: add column hide config #1991\n\n" + }, + { + "comment": "refactor: sparkline cellType set aggregationType None automatically #1999\n\n" + }, + { + "comment": "fix: pivotTable virtual node edit value not work #2002\n\n" + }, + { + "comment": "fix: tooltip content can not be selected #2003\n\n" + }, + { + "comment": "feat: add getCellAtRelativePosition api" + }, + { + "comment": "fix: fix vrender export module" + }, + { + "comment": "fix: fix merge cell update performance problem #1972" + }, + { + "comment": "fix: fix regexp format for webpack 3 #2005" + }, + { + "comment": "fix: fix width computation in shrinkSparklineFirst mode" + } + ] + } + }, { "version": "1.4.1", "tag": "@visactor/vtable_v1.4.1", diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index 99e666d6c..aeab6d496 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, 24 Jun 2024 08:48:40 GMT and should not be manually modified. +This log was last generated on Tue, 02 Jul 2024 12:48:08 GMT and should not be manually modified. + +## 1.4.2 +Tue, 02 Jul 2024 12:48:08 GMT + +### Updates + +- feat: corner title can display row and column diemensionTitle #1926 + + +- fix: when not exit edit state then can not select other cells #1974 + + +- fix: selected_clear event trigger #1981 + + +- feat: add column hide config #1991 + + +- refactor: sparkline cellType set aggregationType None automatically #1999 + + +- fix: pivotTable virtual node edit value not work #2002 + + +- fix: tooltip content can not be selected #2003 + + +- feat: add getCellAtRelativePosition api +- fix: fix vrender export module +- fix: fix merge cell update performance problem #1972 +- fix: fix regexp format for webpack 3 #2005 +- fix: fix width computation in shrinkSparklineFirst mode ## 1.4.1 Mon, 24 Jun 2024 08:48:40 GMT diff --git a/packages/vtable/__tests__/api/listTable-getCellRelativePosition.test.ts b/packages/vtable/__tests__/api/listTable-getCellRelativePosition.test.ts new file mode 100644 index 000000000..900f3ebbf --- /dev/null +++ b/packages/vtable/__tests__/api/listTable-getCellRelativePosition.test.ts @@ -0,0 +1,253 @@ +// @ts-nocheck +// 有问题可对照demo unitTestListTable +import records from '../data/marketsales.json'; +import { ListTable } from '../../src'; +import { createDiv } from '../dom'; +global.__VERSION__ = 'none'; +describe('listTable getCellRect test', () => { + const containerDom: HTMLElement = createDiv(); + containerDom.style.position = 'relative'; + containerDom.style.width = '1000px'; + containerDom.style.height = '800px'; + const columns = [ + { + field: '订单 ID', + caption: '订单 ID', + sort: true, + width: 'auto', + description: '这是订单的描述信息', + style: { + fontFamily: 'Arial', + fontSize: 14 + } + }, + { + field: '订单日期', + caption: '订单日期' + }, + { + field: '发货日期', + caption: '发货日期' + }, + { + field: '客户名称', + caption: '客户名称', + style: { + padding: [10, 0, 10, 60] + } + } + ]; + const option = { + columns: [...columns, ...columns, ...columns, ...columns], + defaultColWidth: 150, + allowFrozenColCount: 5, + frozenColCount: 1, + bottomFrozenRowCount: 2, + rightFrozenColCount: 2 + }; + + option.container = containerDom; + option.records = records; + const listTable = new ListTable(option); + test('listTable getCellAtRelativePosition init', () => { + expect(listTable.getCellAtRelativePosition(100, 220)).toEqual({ + row: 5, + col: 0, + rect: { + left: 0, + right: 151, + top: 200, + bottom: 240, + width: 151, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(800, 220)).toEqual({ + row: 5, + col: 14, + rect: { + top: 200, + bottom: 240, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(400, 220)).toEqual({ + row: 5, + col: 2, + rect: { + left: 301, + right: 451, + top: 200, + bottom: 240, + width: 150, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(100, 740)).toEqual({ + row: 38, + col: 0, + rect: { + left: 0, + right: 151, + width: 151 + } + }); + + expect(listTable.getCellAtRelativePosition(800, 740)).toEqual({ + row: 38, + col: 14, + rect: {} + }); + + expect(listTable.getCellAtRelativePosition(400, 740)).toEqual({ + row: 38, + col: 2, + rect: { + left: 301, + right: 451, + width: 150 + } + }); + + expect(listTable.getCellAtRelativePosition(100, 20)).toEqual({ + row: 0, + col: 0, + rect: { + left: 0, + right: 151, + top: 0, + bottom: 40, + width: 151, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(800, 20)).toEqual({ + row: 0, + col: 14, + rect: { + top: 0, + bottom: 40, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(400, 20)).toEqual({ + row: 0, + col: 2, + rect: { + left: 301, + right: 451, + top: 0, + bottom: 40, + width: 150, + height: 40 + } + }); + }); + + test('listTable getCellAtRelativePosition scroll', () => { + listTable.scrollLeft = 500; + listTable.scrollTop = 500; + expect(listTable.getCellAtRelativePosition(100, 220)).toEqual({ + row: 17, + col: 0, + rect: { + left: 0, + right: 151, + top: 680, + bottom: 720, + width: 151, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(800, 220)).toEqual({ + row: 17, + col: 14, + rect: { + top: 680, + bottom: 720, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(400, 220)).toEqual({ + row: 17, + col: 5, + rect: { + left: 752, + right: 902, + top: 680, + bottom: 720, + width: 150, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(100, 740)).toEqual({ + row: 38, + col: 0, + rect: { + left: 0, + right: 151, + width: 151 + } + }); + + expect(listTable.getCellAtRelativePosition(800, 740)).toEqual({ + row: 38, + col: 14, + rect: {} + }); + + expect(listTable.getCellAtRelativePosition(400, 740)).toEqual({ + row: 38, + col: 5, + rect: { + left: 752, + right: 902, + width: 150 + } + }); + + expect(listTable.getCellAtRelativePosition(100, 20)).toEqual({ + row: 0, + col: 0, + rect: { + left: 0, + right: 151, + top: 0, + bottom: 40, + width: 151, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(800, 20)).toEqual({ + row: 0, + col: 14, + rect: { + top: 0, + bottom: 40, + height: 40 + } + }); + + expect(listTable.getCellAtRelativePosition(400, 20)).toEqual({ + row: 0, + col: 5, + rect: { + left: 752, + right: 902, + top: 0, + bottom: 40, + width: 150, + height: 40 + } + }); + }); +}); diff --git a/packages/vtable/examples/list/list.ts b/packages/vtable/examples/list/list.ts index 144bf154d..06fb2e665 100644 --- a/packages/vtable/examples/list/list.ts +++ b/packages/vtable/examples/list/list.ts @@ -214,6 +214,9 @@ export function createTable() { }; const tableInstance = new VTable.ListTable(option); window.tableInstance = tableInstance; + bindDebugTool(tableInstance.scenegraph.stage, { + customGrapicKeys: ['col', 'row'] + }); tableInstance.on('change_cell_value', arg => { console.log(arg); }); diff --git a/packages/vtable/examples/type/checkbox.ts b/packages/vtable/examples/type/checkbox.ts index 4a66be9c7..89c388c37 100644 --- a/packages/vtable/examples/type/checkbox.ts +++ b/packages/vtable/examples/type/checkbox.ts @@ -9,7 +9,7 @@ export function createTable() { columns: [ { field: '', - // headerType: 'checkbox', + headerType: 'checkbox', cellType: 'checkbox', width: 'auto', checked(args) { @@ -72,6 +72,7 @@ export function createTable() { showFrozenIcon: true, //显示VTable内置冻结列图标 widthMode: 'standard', heightMode: 'autoHeight', + allowFrozenColCount: 3, // transpose: true theme: VTable.themes.DEFAULT.extends({ checkboxStyle: { diff --git a/packages/vtable/package.json b/packages/vtable/package.json index a1e6a88bd..55ef11030 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable", - "version": "1.4.1", + "version": "1.4.2", "description": "canvas table width high performance", "keywords": [ "grid", diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 5e812da11..ae77cd7ec 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -1045,7 +1045,9 @@ export class ListTable extends BaseTable implements ListTableAPI { if (order && field && order !== 'normal') { const sortFunc = this._getSortFuncFromHeaderOption(undefined, field); // 如果sort传入的信息不能生成正确的sortFunc,直接更新表格,避免首次加载无法正常显示内容 - const hd = this.internalProps.layoutMap.headerObjects.find((col: any) => col && col.field === field); + const hd = this.internalProps.layoutMap.headerObjectsIncludeHided.find( + (col: any) => col && col.field === field + ); // hd?.define?.sort && //如果这里也判断 那想要利用sortState来排序 但不显示排序图标就实现不了 if (hd.define.sort !== false) { this.dataSource.sort(hd.field, order, sortFunc ?? defaultOrderFn); diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index fa85cd30d..bd3d02dbb 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -578,24 +578,32 @@ export class PivotTable extends BaseTable implements PivotTableAPI { const cellDimensionPath = this.internalProps.layoutMap.getCellHeaderPaths(col, row); if (cellDimensionPath) { let indicatorPosition: { position: 'col' | 'row'; index?: number }; - const colKeys = cellDimensionPath.colHeaderPaths.map((colPath: any, index: number) => { - if (colPath.indicatorKey) { - indicatorPosition = { - position: 'col', - index - }; - } - return colPath.indicatorKey ?? colPath.value; - }); - const rowKeys = cellDimensionPath.rowHeaderPaths.map((rowPath: any, index: number) => { - if (rowPath.indicatorKey) { - indicatorPosition = { - position: 'row', - index - }; - } - return rowPath.indicatorKey ?? rowPath.value; - }); + const colKeys = cellDimensionPath.colHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((colPath: any, index: number) => { + if (colPath.indicatorKey) { + indicatorPosition = { + position: 'col', + index + }; + } + return colPath.indicatorKey ?? colPath.value; + }); + const rowKeys = cellDimensionPath.rowHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((rowPath: any, index: number) => { + if (rowPath.indicatorKey) { + indicatorPosition = { + position: 'row', + index + }; + } + return rowPath.indicatorKey ?? rowPath.value; + }); const aggregator = this.dataset.getAggregator( // !this.internalProps.layoutMap.indicatorsAsCol ? rowKeys.slice(0, -1) : rowKeys, // this.internalProps.layoutMap.indicatorsAsCol ? colKeys.slice(0, -1) : colKeys, @@ -742,24 +750,32 @@ export class PivotTable extends BaseTable implements PivotTableAPI { } else if (this.dataset) { let indicatorPosition: { position: 'col' | 'row'; index?: number }; const cellDimensionPath = this.internalProps.layoutMap.getCellHeaderPaths(col, row); - const colKeys = cellDimensionPath.colHeaderPaths.map((colPath: any, index: number) => { - if (colPath.indicatorKey) { - indicatorPosition = { - position: 'col', - index - }; - } - return colPath.indicatorKey ?? colPath.value; - }); - const rowKeys = cellDimensionPath.rowHeaderPaths.map((rowPath: any, index: number) => { - if (rowPath.indicatorKey) { - indicatorPosition = { - position: 'row', - index - }; - } - return rowPath.indicatorKey ?? rowPath.value; - }); + const colKeys = cellDimensionPath.colHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((colPath: any, index: number) => { + if (colPath.indicatorKey) { + indicatorPosition = { + position: 'col', + index + }; + } + return colPath.indicatorKey ?? colPath.value; + }); + const rowKeys = cellDimensionPath.rowHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((rowPath: any, index: number) => { + if (rowPath.indicatorKey) { + indicatorPosition = { + position: 'row', + index + }; + } + return rowPath.indicatorKey ?? rowPath.value; + }); const aggregator = this.dataset.getAggregator( // !this.internalProps.layoutMap.indicatorsAsCol ? rowKeys.slice(0, -1) : rowKeys, // this.internalProps.layoutMap.indicatorsAsCol ? colKeys.slice(0, -1) : colKeys, @@ -807,24 +823,32 @@ export class PivotTable extends BaseTable implements PivotTableAPI { } else if (this.dataset) { let indicatorPosition: { position: 'col' | 'row'; index?: number }; const cellDimensionPath = this.internalProps.layoutMap.getCellHeaderPaths(col, row); - const colKeys = cellDimensionPath.colHeaderPaths.map((colPath: any, index: number) => { - if (colPath.indicatorKey) { - indicatorPosition = { - position: 'col', - index - }; - } - return colPath.indicatorKey ?? colPath.value; - }); - const rowKeys = cellDimensionPath.rowHeaderPaths.map((rowPath: any, index: number) => { - if (rowPath.indicatorKey) { - indicatorPosition = { - position: 'row', - index - }; - } - return rowPath.indicatorKey ?? rowPath.value; - }); + const colKeys = cellDimensionPath.colHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((colPath: any, index: number) => { + if (colPath.indicatorKey) { + indicatorPosition = { + position: 'col', + index + }; + } + return colPath.indicatorKey ?? colPath.value; + }); + const rowKeys = cellDimensionPath.rowHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((rowPath: any, index: number) => { + if (rowPath.indicatorKey) { + indicatorPosition = { + position: 'row', + index + }; + } + return rowPath.indicatorKey ?? rowPath.value; + }); const aggregator = this.dataset.getAggregator( // !this.internalProps.layoutMap.indicatorsAsCol ? rowKeys.slice(0, -1) : rowKeys, // this.internalProps.layoutMap.indicatorsAsCol ? colKeys.slice(0, -1) : colKeys, @@ -868,24 +892,32 @@ export class PivotTable extends BaseTable implements PivotTableAPI { } else if (this.dataset) { let indicatorPosition: { position: 'col' | 'row'; index?: number }; const cellDimensionPath = this.internalProps.layoutMap.getCellHeaderPaths(col, row); - const colKeys = cellDimensionPath.colHeaderPaths.map((colPath: any, index: number) => { - if (colPath.indicatorKey) { - indicatorPosition = { - position: 'col', - index - }; - } - return colPath.indicatorKey ?? colPath.value; - }); - const rowKeys = cellDimensionPath.rowHeaderPaths.map((rowPath: any, index: number) => { - if (rowPath.indicatorKey) { - indicatorPosition = { - position: 'row', - index - }; - } - return rowPath.indicatorKey ?? rowPath.value; - }); + const colKeys = cellDimensionPath.colHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((colPath: any, index: number) => { + if (colPath.indicatorKey) { + indicatorPosition = { + position: 'col', + index + }; + } + return colPath.indicatorKey ?? colPath.value; + }); + const rowKeys = cellDimensionPath.rowHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((rowPath: any, index: number) => { + if (rowPath.indicatorKey) { + indicatorPosition = { + position: 'row', + index + }; + } + return rowPath.indicatorKey ?? rowPath.value; + }); const aggregator = this.dataset.getAggregator( // !this.internalProps.layoutMap.indicatorsAsCol ? rowKeys.slice(0, -1) : rowKeys, // this.internalProps.layoutMap.indicatorsAsCol ? colKeys.slice(0, -1) : colKeys, @@ -928,24 +960,32 @@ export class PivotTable extends BaseTable implements PivotTableAPI { } else if (this.dataset) { let indicatorPosition: { position: 'col' | 'row'; index?: number }; const cellDimensionPath = this.internalProps.layoutMap.getCellHeaderPaths(col, row); - const colKeys = cellDimensionPath.colHeaderPaths.map((colPath: any, index: number) => { - if (colPath.indicatorKey) { - indicatorPosition = { - position: 'col', - index - }; - } - return colPath.indicatorKey ?? colPath.value; - }); - const rowKeys = cellDimensionPath.rowHeaderPaths.map((rowPath: any, index: number) => { - if (rowPath.indicatorKey) { - indicatorPosition = { - position: 'row', - index - }; - } - return rowPath.indicatorKey ?? rowPath.value; - }); + const colKeys = cellDimensionPath.colHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((colPath: any, index: number) => { + if (colPath.indicatorKey) { + indicatorPosition = { + position: 'col', + index + }; + } + return colPath.indicatorKey ?? colPath.value; + }); + const rowKeys = cellDimensionPath.rowHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((rowPath: any, index: number) => { + if (rowPath.indicatorKey) { + indicatorPosition = { + position: 'row', + index + }; + } + return rowPath.indicatorKey ?? rowPath.value; + }); const aggregator = this.dataset.getAggregator( // !this.internalProps.layoutMap.indicatorsAsCol ? rowKeys.slice(0, -1) : rowKeys, // this.internalProps.layoutMap.indicatorsAsCol ? colKeys.slice(0, -1) : colKeys, @@ -1784,12 +1824,20 @@ export class PivotTable extends BaseTable implements PivotTableAPI { newValue ); } else { - const colKeys = cellDimensionPath.colHeaderPaths.map((colPath: any) => { - return colPath.indicatorKey ?? colPath.value; - }); - const rowKeys = cellDimensionPath.rowHeaderPaths.map((rowPath: any) => { - return rowPath.indicatorKey ?? rowPath.value; - }); + const colKeys = cellDimensionPath.colHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((colPath: any) => { + return colPath.indicatorKey ?? colPath.value; + }); + const rowKeys = cellDimensionPath.rowHeaderPaths + ?.filter((path: any) => { + return !path.virtual; + }) + .map((rowPath: any) => { + return rowPath.indicatorKey ?? rowPath.value; + }); this.dataset.changeTreeNodeValue( !this.internalProps.layoutMap.indicatorsAsCol ? rowKeys.slice(0, -1) : rowKeys, this.internalProps.layoutMap.indicatorsAsCol ? colKeys.slice(0, -1) : colKeys, diff --git a/packages/vtable/src/components/tooltip/logic/BubbleTooltipElement.ts b/packages/vtable/src/components/tooltip/logic/BubbleTooltipElement.ts index bcaeff0c7..407eb7b1f 100644 --- a/packages/vtable/src/components/tooltip/logic/BubbleTooltipElement.ts +++ b/packages/vtable/src/components/tooltip/logic/BubbleTooltipElement.ts @@ -3,7 +3,7 @@ import type { RectProps } from '../../../ts-types'; import { Placement } from '../../../ts-types'; import { createElement } from '../../../tools/dom'; import { importStyle } from './BubbleTooltipElementStyle'; -import { isMobile } from '../../../tools/util'; +import { isDivSelected, isMobile } from '../../../tools/util'; import type { TooltipOptions } from '../../../ts-types/tooltip'; import type { BaseTableAPI } from '../../../ts-types/base-table'; importStyle(); @@ -41,6 +41,12 @@ export class BubbleTooltipElement { messageElement.addEventListener('wheel', e => { e.stopPropagation(); }); + messageElement.addEventListener('copy', e => { + const isSelected = isDivSelected(messageElement as HTMLDivElement); // 判断tooltip弹框内容是否有选中 + if (isSelected) { + e.stopPropagation(); + } + }); } bindToCell( table: BaseTableAPI, diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 7f5d485b8..eac648d8f 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -97,8 +97,6 @@ import { IconCache } from '../plugins/icons'; import { _applyColWidthLimits, _getScrollableVisibleRect, - _getTargetFrozenColAt, - _getTargetFrozenRowAt, _setDataSource, _setRecords, _toPxWidth, @@ -132,6 +130,16 @@ import { ReactCustomLayout } from '../components/react/react-custom-layout'; import type { ISortedMapItem } from '../data/DataSource'; import { hasAutoImageColumn } from '../layout/layout-helper'; import { Factory } from './factory'; +import { + getCellAt, + getCellAtRelativePosition, + getColAt, + getRowAt, + getTargetColAt, + getTargetColAtConsiderRightFrozen, + getTargetRowAt, + getTargetRowAtConsiderBottomFrozen +} from './utils/get-cell-position'; const { toBoxArray } = utilStyle; const { isTouchEvent } = event; @@ -1776,20 +1784,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @returns */ getRowAt(absoluteY: number): { top: number; row: number; bottom: number; height: number } { - const frozen = _getTargetFrozenRowAt(this, absoluteY); - if (frozen) { - return frozen; - } - let row = this.getTargetRowAt(absoluteY); - if (!row) { - row = { - top: -1, - row: -1, - bottom: -1, - height: -1 - }; - } - return row; + return getRowAt(absoluteY, this); } /** * 根据x值计算所在列 @@ -1797,20 +1792,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @returns */ getColAt(absoluteX: number): { left: number; col: number; right: number; width: number } { - const frozen = _getTargetFrozenColAt(this, absoluteX); - if (frozen) { - return frozen; - } - let col = this.getTargetColAt(absoluteX); - if (!col) { - col = { - left: -1, - col: -1, - right: -1, - width: 1 - }; - } - return col; + return getColAt(absoluteX, this); } /** * 根据坐标值获取行列位置,index和rect范围 @@ -1819,23 +1801,18 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @returns */ getCellAt(absoluteX: number, absoluteY: number): CellAddressWithBound { - const rowInfo = this.getRowAt(absoluteY); - const { row, top, bottom, height } = rowInfo; - const colInfo = this.getColAt(absoluteX); - const { col, left, right, width } = colInfo; - const rect = { - left, - right, - top, - bottom, - width, - height - }; - return { - row, - col, - rect - }; + return getCellAt(absoluteX, absoluteY, this); + } + + /** + * 获取屏幕坐标对应的单元格信息,考虑滚动 + * @param this + * @param relativeX 左边x值,相对于容器左上角,考虑表格滚动 + * @param relativeY 左边y值,相对于容器左上角,考虑表格滚动 + * @returns + */ + getCellAtRelativePosition(relativeX: number, relativeY: number): CellAddressWithBound { + return getCellAtRelativePosition(relativeX, relativeY, this); } /** * 检查行列号是否正确 @@ -2497,68 +2474,9 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @param absoluteX * @returns */ + getTargetColAt(absoluteX: number): ColumnInfo | null { - if (absoluteX === 0) { - return { left: 0, col: 0, right: 0, width: 0 }; - } - const findBefore = ( - startCol: number, - startRight: number - ): { - left: number; - col: number; - right: number; - width: number; - } | null => { - let right = startRight; - for (let col = startCol; col >= 0; col--) { - const width = this.getColWidth(col); - const left = right - width; - if (Math.round(left) <= Math.round(absoluteX) && Math.round(absoluteX) < Math.round(right)) { - return { - left, - col, - right, - width - }; - } - right = left; - } - return null; - }; - const findAfter = ( - startCol: number, - startRight: number - ): { - left: number; - col: number; - right: number; - width: number; - } | null => { - let left = startRight - this.getColWidth(startCol); - const { colCount } = this.internalProps; - for (let col = startCol; col < colCount; col++) { - const width = this.getColWidth(col); - const right = left + width; - if (Math.round(left) <= Math.round(absoluteX) && Math.round(absoluteX) < Math.round(right)) { - return { - left, - col, - right, - width - }; - } - left = right; - } - return null; - }; - //计算这个位置处是第几行 - const candCol = this.computeTargetColByX(absoluteX); - const right = this.getColsWidth(0, candCol); - if (absoluteX >= right) { - return findAfter(candCol, right); - } - return findBefore(candCol, right); + return getTargetColAt(absoluteX, this); } /** * 根据y获取该位置所处行值 @@ -2566,73 +2484,9 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @param absoluteX * @returns */ + getTargetRowAt(absoluteY: number): RowInfo | null { - if (absoluteY === 0) { - return { top: 0, row: 0, bottom: 0, height: 0 }; - } - - const findBefore = ( - startRow: number, - startBottom: number - ): { - top: number; - row: number; - bottom: number; - height: number; - } | null => { - let bottom = startBottom; - for (let row = startRow; row >= 0; row--) { - const height = this.getRowHeight(row); - const top = bottom - height; - if (Math.round(top) <= Math.round(absoluteY) && Math.round(absoluteY) < Math.round(bottom)) { - return { - top, - row, - bottom, - height - }; - } - bottom = top; - } - return null; - }; - const findAfter = ( - startRow: number, - startBottom: number - ): { - top: number; - row: number; - bottom: number; - height: number; - } | null => { - let top = startBottom - this.getRowHeight(startRow); - const { rowCount } = this.internalProps; - for (let row = startRow; row < rowCount; row++) { - const height = this.getRowHeight(row); - const bottom = top + height; - if (Math.round(top) <= Math.round(absoluteY) && Math.round(absoluteY) < Math.round(bottom)) { - return { - top, - row, - bottom, - height - }; - } - top = bottom; - } - return null; - }; - // const candRow = Math.min( - // Math.ceil(absoluteY / this.internalProps.defaultRowHeight), - // this.rowCount - 1 - // ); - //计算这个位置处是第几行 - const candRow = this.computeTargetRowByY(absoluteY); - const bottom = this.getRowsHeight(0, candRow); - if (absoluteY >= bottom) { - return findAfter(candRow, bottom); - } - return findBefore(candRow, bottom); + return getTargetRowAt(absoluteY, this); } /** @@ -2641,27 +2495,9 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @param absoluteX * @returns */ + getTargetColAtConsiderRightFrozen(absoluteX: number, isConsider: boolean): ColumnInfo | null { - if (absoluteX === 0) { - return { left: 0, col: 0, right: 0, width: 0 }; - } - if ( - isConsider && - absoluteX > this.tableNoFrameWidth - this.getRightFrozenColsWidth() && - absoluteX < this.tableNoFrameWidth - ) { - for (let i = 0; i < this.rightFrozenColCount; i++) { - if (absoluteX > this.tableNoFrameWidth - this.getColsWidth(this.colCount - i - 1, this.colCount - 1)) { - return { - col: this.colCount - i - 1, - left: undefined, - right: undefined, - width: undefined - }; - } - } - } - return this.getTargetColAt(absoluteX); + return getTargetColAtConsiderRightFrozen(absoluteX, isConsider, this); } /** @@ -2670,84 +2506,11 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @param absoluteX * @returns */ + getTargetRowAtConsiderBottomFrozen(absoluteY: number, isConsider: boolean): RowInfo | null { - if (absoluteY === 0) { - return { top: 0, row: 0, bottom: 0, height: 0 }; - } - if ( - isConsider && - absoluteY > this.tableNoFrameHeight - this.getBottomFrozenRowsHeight() && - absoluteY < this.tableNoFrameHeight - ) { - for (let i = 0; i < this.rightFrozenColCount; i++) { - if (absoluteY > this.tableNoFrameHeight - this.getRowsHeight(this.rowCount - i - 1, this.rowCount - 1)) { - return { - row: this.rowCount - i - 1, - top: undefined, - bottom: undefined, - height: undefined - }; - } - } - } - return this.getTargetRowAt(absoluteY); + return getTargetRowAtConsiderBottomFrozen(absoluteY, isConsider, this); } - /** - * 根据y值(包括了scroll的)计算所在行 - * @param this - * @param absoluteY 左边y值,包含了scroll滚动距离 - * @returns - */ - private computeTargetRowByY(absoluteY: number): number { - let defaultRowHeight = this.internalProps.defaultRowHeight; - - //使用二分法计算出row - if (this._rowRangeHeightsMap.get(`$0$${this.rowCount - 1}`)) { - defaultRowHeight = this._rowRangeHeightsMap.get(`$0$${this.rowCount - 1}`) / this.rowCount; - // let startRow = 0; - // let endRow = this.rowCount - 1; - // while (endRow - startRow > 1) { - // const midRow = Math.floor((startRow + endRow) / 2); - // if (absoluteY < this._rowRangeHeightsMap.get(`$0$${midRow}`)) { - // endRow = midRow; - // } else if (absoluteY > this._rowRangeHeightsMap.get(`$0$${midRow}`)) { - // startRow = midRow; - // } else { - // return midRow; - // } - // } - // return endRow; - } - //否则使用defaultRowHeight大约计算一个row - return Math.min(Math.ceil(absoluteY / defaultRowHeight), this.rowCount - 1); - } - /** - * 根据x值(包括了scroll的)计算所在列 主要借助colRangeWidthsMap缓存来提高计算效率 - * @param this - * @param absoluteX 左边x值,包含了scroll滚动距离 - * @returns - */ - private computeTargetColByX(absoluteX: number): number { - //使用二分法计算出col - if (this._colRangeWidthsMap.get(`$0$${this.colCount - 1}`)) { - let startCol = 0; - let endCol = this.colCount - 1; - while (endCol - startCol > 1) { - const midCol = Math.floor((startCol + endCol) / 2); - if (absoluteX < this._colRangeWidthsMap.get(`$0$${midCol}`)) { - endCol = midCol; - } else if (absoluteX > this._colRangeWidthsMap.get(`$0$${midCol}`)) { - startCol = midCol; - } else { - return midCol; - } - } - return endCol; - } - //否则使用defaultColWidth大约计算一个col - return Math.min(Math.ceil(absoluteX / this.internalProps.defaultColWidth), this.colCount - 1); - } /** * 清除选中单元格 */ @@ -2760,8 +2523,9 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @param row */ selectCell(col: number, row: number, isShift?: boolean, isCtrl?: boolean) { + const isHasSelected = !!this.stateManager.select.ranges?.length; this.stateManager.updateSelectPos(col, row, isShift, isCtrl); - this.stateManager.endSelectCells(); + this.stateManager.endSelectCells(true, isHasSelected); } /** * 选中单元格区域,可设置多个区域同时选中 @@ -2793,7 +2557,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.stateManager.updateInteractionState(InteractionState.grabing); this.stateManager.updateSelectPos(cellRange.end.col, cellRange.end.row, false, index >= 1, false, false, true); } - this.stateManager.endSelectCells(false); + this.stateManager.endSelectCells(false, false); this.stateManager.updateInteractionState(InteractionState.default); }); // 选择后 会自动滚动到所选区域最后一行一列的位置 这里再设置回滚动前位置 diff --git a/packages/vtable/src/core/utils/get-cell-position.ts b/packages/vtable/src/core/utils/get-cell-position.ts new file mode 100644 index 000000000..a176b7631 --- /dev/null +++ b/packages/vtable/src/core/utils/get-cell-position.ts @@ -0,0 +1,415 @@ +import type { CellAddressWithBound } from '../../ts-types'; +import type { BaseTableAPI } from '../../ts-types/base-table'; +import { _getTargetFrozenColAt, _getTargetFrozenRowAt } from '../tableHelper'; + +/** + * 根据y值计算所在行 + * @param absoluteY 相对于表格左上角的y坐标(无滚动) + * @returns + */ +export function getRowAt( + absoluteY: number, + _this: BaseTableAPI +): { top: number; row: number; bottom: number; height: number } { + const frozen = _getTargetFrozenRowAt(_this as any, absoluteY); + if (frozen) { + return frozen; + } + let row = getTargetRowAt(absoluteY, _this); + if (!row) { + row = { + top: -1, + row: -1, + bottom: -1, + height: -1 + }; + } + return row; +} + +/** + * 根据x值计算所在列 + * @param absoluteX 相对于表格左上角的x坐标(无滚动) + * @returns + */ +export function getColAt( + absoluteX: number, + _this: BaseTableAPI +): { left: number; col: number; right: number; width: number } { + const frozen = _getTargetFrozenColAt(_this as any, absoluteX); + if (frozen) { + return frozen; + } + let col = getTargetColAt(absoluteX, _this); + if (!col) { + col = { + left: -1, + col: -1, + right: -1, + width: 1 + }; + } + return col; +} +/** + * 根据坐标值获取行列位置,index和rect范围 + * @param absoluteX 表格左上角的x坐标(无滚动) + * @param absoluteY 表格左上角的y坐标(无滚动) + * @returns + */ +export function getCellAt(absoluteX: number, absoluteY: number, _this: BaseTableAPI): CellAddressWithBound { + const rowInfo = getRowAt(absoluteY, _this); + const { row, top, bottom, height } = rowInfo; + const colInfo = getColAt(absoluteX, _this); + const { col, left, right, width } = colInfo; + const rect = { + left, + right, + top, + bottom, + width, + height + }; + return { + row, + col, + rect + }; +} + +/** + * 根据x获取该位置所处列值 + * @param table + * @param absoluteX 表格左上角的x坐标(无滚动) + * @returns + */ +export function getTargetColAt( + absoluteX: number, + _this: BaseTableAPI +): { col: number; left: number; right: number; width: number } | null { + if (absoluteX === 0) { + return { left: 0, col: 0, right: 0, width: 0 }; + } + const findBefore = ( + startCol: number, + startRight: number + ): { + left: number; + col: number; + right: number; + width: number; + } | null => { + let right = startRight; + for (let col = startCol; col >= 0; col--) { + const width = _this.getColWidth(col); + const left = right - width; + if (Math.round(left) <= Math.round(absoluteX) && Math.round(absoluteX) < Math.round(right)) { + return { + left, + col, + right, + width + }; + } + right = left; + } + return null; + }; + const findAfter = ( + startCol: number, + startRight: number + ): { + left: number; + col: number; + right: number; + width: number; + } | null => { + let left = startRight - _this.getColWidth(startCol); + const { colCount } = _this.internalProps; + for (let col = startCol; col < colCount; col++) { + const width = _this.getColWidth(col); + const right = left + width; + if (Math.round(left) <= Math.round(absoluteX) && Math.round(absoluteX) < Math.round(right)) { + return { + left, + col, + right, + width + }; + } + left = right; + } + return null; + }; + //计算这个位置处是第几行 + const candCol = computeTargetColByX(absoluteX, _this); + const right = _this.getColsWidth(0, candCol); + if (absoluteX >= right) { + return findAfter(candCol, right); + } + return findBefore(candCol, right); +} + +/** + * 根据y获取该位置所处行值 + * @param table + * @param absoluteX 表格左上角的y坐标(无滚动) + * @returns + */ +export function getTargetRowAt( + absoluteY: number, + _this: BaseTableAPI +): { row: number; top: number; bottom: number; height: number } | null { + if (absoluteY === 0) { + return { top: 0, row: 0, bottom: 0, height: 0 }; + } + + const findBefore = ( + startRow: number, + startBottom: number + ): { + top: number; + row: number; + bottom: number; + height: number; + } | null => { + let bottom = startBottom; + for (let row = startRow; row >= 0; row--) { + const height = _this.getRowHeight(row); + const top = bottom - height; + if (Math.round(top) <= Math.round(absoluteY) && Math.round(absoluteY) < Math.round(bottom)) { + return { + top, + row, + bottom, + height + }; + } + bottom = top; + } + return null; + }; + const findAfter = ( + startRow: number, + startBottom: number + ): { + top: number; + row: number; + bottom: number; + height: number; + } | null => { + let top = startBottom - _this.getRowHeight(startRow); + const { rowCount } = _this.internalProps; + for (let row = startRow; row < rowCount; row++) { + const height = _this.getRowHeight(row); + const bottom = top + height; + if (Math.round(top) <= Math.round(absoluteY) && Math.round(absoluteY) < Math.round(bottom)) { + return { + top, + row, + bottom, + height + }; + } + top = bottom; + } + return null; + }; + // const candRow = Math.min( + // Math.ceil(absoluteY / this.internalProps.defaultRowHeight), + // this.rowCount - 1 + // ); + //计算这个位置处是第几行 + const candRow = computeTargetRowByY(absoluteY, _this); + const bottom = _this.getRowsHeight(0, candRow); + if (absoluteY >= bottom) { + return findAfter(candRow, bottom); + } + return findBefore(candRow, bottom); +} + +/** + * 根据x获取右侧冻结中该位置所处列值 + * @param table + * @param absoluteX 屏幕坐标x值 + * @returns + */ +export function getTargetColAtConsiderRightFrozen( + absoluteX: number, + isConsider: boolean, + _this: BaseTableAPI +): { col: number; left: number; right: number; width: number } | null { + if (absoluteX === 0) { + return { left: 0, col: 0, right: 0, width: 0 }; + } + if ( + isConsider && + absoluteX > _this.tableNoFrameWidth - _this.getRightFrozenColsWidth() && + absoluteX < _this.tableNoFrameWidth + ) { + for (let i = 0; i < _this.rightFrozenColCount; i++) { + if (absoluteX > _this.tableNoFrameWidth - _this.getColsWidth(_this.colCount - i - 1, _this.colCount - 1)) { + return { + col: _this.colCount - i - 1, + left: undefined, + right: undefined, + width: undefined + }; + } + } + } + return getTargetColAt(absoluteX, _this); +} + +/** + * 根据y获取底部冻结该位置所处行值 + * @param table + * @param absoluteX 屏幕坐标y值 + * @returns + */ +export function getTargetRowAtConsiderBottomFrozen( + absoluteY: number, + isConsider: boolean, + _this: BaseTableAPI +): { row: number; top: number; bottom: number; height: number } | null { + if (absoluteY === 0) { + return { top: 0, row: 0, bottom: 0, height: 0 }; + } + if ( + isConsider && + absoluteY > _this.tableNoFrameHeight - _this.getBottomFrozenRowsHeight() && + absoluteY < _this.tableNoFrameHeight + ) { + for (let i = 0; i < _this.rightFrozenColCount; i++) { + if (absoluteY > _this.tableNoFrameHeight - _this.getRowsHeight(_this.rowCount - i - 1, _this.rowCount - 1)) { + return { + row: _this.rowCount - i - 1, + top: undefined, + bottom: undefined, + height: undefined + }; + } + } + } + return getTargetRowAt(absoluteY, _this); +} + +/** + * 根据y值(包括了scroll的)计算所在行 + * @param this + * @param absoluteY 左边y值,包含了scroll滚动距离 + * @returns + */ +export function computeTargetRowByY(absoluteY: number, _this: BaseTableAPI): number { + let defaultRowHeight = _this.internalProps.defaultRowHeight; + + //使用二分法计算出row + if (_this._rowRangeHeightsMap.get(`$0$${_this.rowCount - 1}`)) { + defaultRowHeight = _this._rowRangeHeightsMap.get(`$0$${_this.rowCount - 1}`) / _this.rowCount; + // let startRow = 0; + // let endRow = this.rowCount - 1; + // while (endRow - startRow > 1) { + // const midRow = Math.floor((startRow + endRow) / 2); + // if (absoluteY < this._rowRangeHeightsMap.get(`$0$${midRow}`)) { + // endRow = midRow; + // } else if (absoluteY > this._rowRangeHeightsMap.get(`$0$${midRow}`)) { + // startRow = midRow; + // } else { + // return midRow; + // } + // } + // return endRow; + } + //否则使用defaultRowHeight大约计算一个row + return Math.min(Math.ceil(absoluteY / defaultRowHeight), _this.rowCount - 1); +} + +/** + * 根据x值(包括了scroll的)计算所在列 主要借助colRangeWidthsMap缓存来提高计算效率 + * @param this + * @param absoluteX 左边x值,包含了scroll滚动距离 + * @returns + */ +export function computeTargetColByX(absoluteX: number, _this: BaseTableAPI): number { + //使用二分法计算出col + if (_this._colRangeWidthsMap.get(`$0$${_this.colCount - 1}`)) { + let startCol = 0; + let endCol = _this.colCount - 1; + while (endCol - startCol > 1) { + const midCol = Math.floor((startCol + endCol) / 2); + if (absoluteX < _this._colRangeWidthsMap.get(`$0$${midCol}`)) { + endCol = midCol; + } else if (absoluteX > _this._colRangeWidthsMap.get(`$0$${midCol}`)) { + startCol = midCol; + } else { + return midCol; + } + } + return endCol; + } + //否则使用defaultColWidth大约计算一个col + return Math.min(Math.ceil(absoluteX / _this.internalProps.defaultColWidth), _this.colCount - 1); +} + +/** + * 获取屏幕坐标对应的单元格信息,考虑滚动 + * @param this + * @param relativeX 左边x值,相对于容器左上角,考虑表格滚动 + * @param relativeY 左边y值,相对于容器左上角,考虑表格滚动 + * @returns + */ +export function getCellAtRelativePosition(x: number, y: number, _this: BaseTableAPI): CellAddressWithBound { + // table border and outer component + x -= _this.tableX; + y -= _this.tableY; + + // top frozen + let topFrozen = false; + if (y > 0 && y < _this.getFrozenRowsHeight()) { + topFrozen = true; + } + + // left frozen + let leftFrozen = false; + if (x > 0 && x < _this.getFrozenColsWidth()) { + leftFrozen = true; + } + + // bottom frozen + let bottomFrozen = false; + if (y > _this.tableNoFrameHeight - _this.getBottomFrozenRowsHeight() && y < _this.tableNoFrameHeight) { + bottomFrozen = true; + } + // right frozen + let rightFrozen = false; + if (x > _this.tableNoFrameWidth - _this.getRightFrozenColsWidth() && x < _this.tableNoFrameWidth) { + rightFrozen = true; + } + + const colInfo = getTargetColAtConsiderRightFrozen( + leftFrozen || rightFrozen ? x : x + _this.scrollLeft, + rightFrozen, + _this + ); + const rowInfo = getTargetRowAtConsiderBottomFrozen( + topFrozen || bottomFrozen ? y : y + _this.scrollTop, + bottomFrozen, + _this + ); + + const { row, top, bottom, height } = rowInfo; + const { col, left, right, width } = colInfo; + const rect = { + left, + right, + top, + bottom, + width, + height + }; + return { + row, + col, + rect + }; +} diff --git a/packages/vtable/src/edit/edit-manager.ts b/packages/vtable/src/edit/edit-manager.ts index 7b2562095..28574712e 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -108,10 +108,10 @@ export class EditManeger { } } - /** 如果是事件触发调用该接口 请传入原始事件对象 将判断事件对象是否在编辑器本身上面 来处理是否结束编辑 */ - completeEdit(e?: Event) { + /** 如果是事件触发调用该接口 请传入原始事件对象 将判断事件对象是否在编辑器本身上面 来处理是否结束编辑 返回值如果为false说明没有退出编辑状态*/ + completeEdit(e?: Event): boolean { if (!this.editingEditor) { - return; + return true; } const target = e?.target as HTMLElement | undefined; @@ -122,10 +122,10 @@ export class EditManeger { console.warn('VTable Warn: `targetIsOnEditor` is deprecated, please use `isEditorElement` instead.'); if (editor.targetIsOnEditor(target)) { - return; + return false; } } else if (!editor.isEditorElement || editor.isEditorElement(target)) { - return; + return false; } } @@ -148,7 +148,9 @@ export class EditManeger { this.editingEditor.exit?.(); this.editingEditor.onEnd?.(); this.editingEditor = null; + return true; } + return false; } cancelEdit() { diff --git a/packages/vtable/src/event/event.ts b/packages/vtable/src/event/event.ts index 294849caa..1c356f652 100644 --- a/packages/vtable/src/event/event.ts +++ b/packages/vtable/src/event/event.ts @@ -129,7 +129,9 @@ export class EventManager { } else if (funcType === IconFuncTypeEnum.drillDown) { drillClick(this.table); } else if (funcType === IconFuncTypeEnum.collapse || funcType === IconFuncTypeEnum.expand) { - this.table.stateManager.updateSelectPos(-1, -1); + const isHasSelected = !!stateManager.select.ranges?.length; + stateManager.updateSelectPos(-1, -1); + stateManager.endSelectCells(true, isHasSelected); this.table.toggleHierarchyState(col, row); } }); diff --git a/packages/vtable/src/event/listener/container-dom.ts b/packages/vtable/src/event/listener/container-dom.ts index a89cb3623..85c9a7981 100644 --- a/packages/vtable/src/event/listener/container-dom.ts +++ b/packages/vtable/src/event/listener/container-dom.ts @@ -120,23 +120,26 @@ export function bindContainerDomListener(eventManager: EventManager) { (table as ListTableAPI).editorManager.cancelEdit(); } else if (e.key === 'Enter') { // 如果按enter键 可以结束当前的编辑 或开启编辑选中的单元格(仅限单选) - if ((table as ListTableAPI).editorManager?.editingEditor) { + if ((table as ListTableAPI).editorManager.editingEditor) { + // 如果是结束当前编辑,且有主动监听keydown事件,则先触发keydown事件,之后再结束编辑 + handleKeydownListener(e); (table as ListTableAPI).editorManager.completeEdit(); table.getElement().focus(); - } else { - if ( - (table.options.keyboardOptions?.editCellOnEnter ?? true) && - (table.stateManager.select.ranges?.length ?? 0) === 1 - ) { - // 如果开启按enter键进入编辑的配置 且当前有选中的单元格 则进入编辑 - const startCol = table.stateManager.select.ranges[0].start.col; - const startRow = table.stateManager.select.ranges[0].start.row; - const endCol = table.stateManager.select.ranges[0].end.col; - const endRow = table.stateManager.select.ranges[0].end.row; - if (startCol === endCol && startRow === endRow) { - if ((table as ListTableAPI).getEditor(startCol, startRow)) { - (table as ListTableAPI).editorManager.startEditCell(startCol, startRow); - } + // 直接返回,不再触发最后的keydown监听事件相关代码 + return; + } + if ( + (table.options.keyboardOptions?.editCellOnEnter ?? true) && + (table.stateManager.select.ranges?.length ?? 0) === 1 + ) { + // 如果开启按enter键进入编辑的配置 且当前有选中的单元格 则进入编辑(仅限单选) + const startCol = table.stateManager.select.ranges[0].start.col; + const startRow = table.stateManager.select.ranges[0].start.row; + const endCol = table.stateManager.select.ranges[0].end.col; + const endRow = table.stateManager.select.ranges[0].end.row; + if (startCol === endCol && startRow === endRow) { + if ((table as ListTableAPI).getEditor(startCol, startRow)) { + (table as ListTableAPI).editorManager.startEditCell(startCol, startRow); } } } @@ -168,6 +171,13 @@ export function bindContainerDomListener(eventManager: EventManager) { } } + handleKeydownListener(e); + }); + /** + * 处理主动注册的keydown事件 + * @param e + */ + function handleKeydownListener(e: KeyboardEvent) { if ((table as any).hasListeners(TABLE_EVENT_TYPE.KEYDOWN)) { const cellsEvent: KeydownEvent = { keyCode: e.keyCode ?? e.which, @@ -178,7 +188,7 @@ export function bindContainerDomListener(eventManager: EventManager) { }; table.fireListeners(TABLE_EVENT_TYPE.KEYDOWN, cellsEvent); } - }); + } handler.on(table.getElement(), 'copy', (e: KeyboardEvent) => { if (table.keyboardOptions?.copySelected) { @@ -317,6 +327,12 @@ export function bindContainerDomListener(eventManager: EventManager) { table.resize(); } }); + + // const regex = /]*>(.*?)<\/tr>/gs; // 匹配标签及其内容 + const regex = /]*>([\s\S]*?)<\/tr>/g; // for webpack3 + // const cellRegex = /]*>(.*?)<\/td>/gs; // 匹配标签及其内容 + const cellRegex = /]*>([\s\S]*?)<\/td>/g; // for webpack3 + function pasteHtmlToTable(item: ClipboardItem) { const ranges = table.stateManager.select.ranges; const selectRangeLength = ranges.length; @@ -331,12 +347,10 @@ export function bindContainerDomListener(eventManager: EventManager) { blob.text().then((pastedData: any) => { // 解析html数据 if (pastedData && /(]*>(.*?)<\/tr>/gs; // 匹配标签及其内容 // const matches = pastedData.matchAll(regex); const matches = Array.from(pastedData.matchAll(regex)); for (const match of matches) { const rowContent = match[1]; // 获取标签中的内容 - const cellRegex = /]*>(.*?)<\/td>/gs; // 匹配标签及其内容 const cellMatches: RegExpMatchArray[] = Array.from(rowContent.matchAll(cellRegex)); // 获取标签中的内容 const rowValues = cellMatches.map(cellMatch => { return ( diff --git a/packages/vtable/src/event/listener/table-group.ts b/packages/vtable/src/event/listener/table-group.ts index b41964d52..a84d1c3ef 100644 --- a/packages/vtable/src/event/listener/table-group.ts +++ b/packages/vtable/src/event/listener/table-group.ts @@ -341,12 +341,18 @@ export function bindTableGroupListener(eventManager: EventManager) { } } } - (table as ListTableAPI).editorManager?.completeEdit(e.nativeEvent); + const isCompleteEdit = (table as ListTableAPI).editorManager?.completeEdit(e.nativeEvent); + if (isCompleteEdit === false) { + // 如果没有正常退出编辑状态 则不执行下面的逻辑 如选择其他单元格的逻辑 + return; + } stateManager.updateInteractionState(InteractionState.default); eventManager.dealTableHover(); //点击到表格外部不需要取消选中状态 if (table.options.select?.outsideClickDeselect) { + const isHasSelected = !!stateManager.select.ranges?.length; eventManager.dealTableSelect(); + stateManager.endSelectCells(true, isHasSelected); } }); @@ -386,8 +392,11 @@ export function bindTableGroupListener(eventManager: EventManager) { // 点击在menu外,且不是下拉菜单的icon,移除menu stateManager.hideMenu(); } - (table as ListTableAPI).editorManager?.completeEdit(e.nativeEvent); - + const isCompleteEdit = (table as ListTableAPI).editorManager?.completeEdit(e.nativeEvent); + if (isCompleteEdit === false) { + // 如果没有正常退出编辑状态 则不执行下面的逻辑 如选择其他单元格的逻辑 + return; + } const hitIcon = (eventArgsSet?.eventArgs?.target as any)?.role?.startsWith('icon') ? eventArgsSet.eventArgs.target : (e.target as any).role?.startsWith('icon') @@ -635,8 +644,9 @@ export function bindTableGroupListener(eventManager: EventManager) { const eventArgsSet: SceneEvent = getCellEventArgsSet(e); if (eventManager.touchSetTimeout) { clearTimeout(eventManager.touchSetTimeout); + const isHasSelected = !!stateManager.select.ranges?.length; eventManager.dealTableSelect(eventArgsSet); - stateManager.endSelectCells(); + stateManager.endSelectCells(true, isHasSelected); eventManager.touchSetTimeout = undefined; } } @@ -679,7 +689,11 @@ export function bindTableGroupListener(eventManager: EventManager) { if ((eventArgsSet.eventArgs?.target as any) !== stateManager.residentHoverIcon?.icon) { stateManager.hideMenu(); } - (table as ListTableAPI).editorManager?.completeEdit(e.nativeEvent); + const isCompleteEdit = (table as ListTableAPI).editorManager?.completeEdit(e.nativeEvent); + if (isCompleteEdit === false) { + // 如果没有正常退出编辑状态 则不执行下面的逻辑 如选择其他单元格的逻辑 + return; + } const hitIcon = (e.target as any).role?.startsWith('icon') ? e.target : undefined; eventManager.downIcon = hitIcon; @@ -687,6 +701,7 @@ export function bindTableGroupListener(eventManager: EventManager) { if ( !hitIcon && !eventManager.checkCellFillhandle(eventArgsSet) && + !stateManager.columnResize.resizing && eventManager.checkColumnResize(eventArgsSet, true) ) { // eventManager.startColumnResize(e); @@ -738,12 +753,12 @@ export function bindTableGroupListener(eventManager: EventManager) { ) { stateManager.updateInteractionState(InteractionState.default); eventManager.dealTableHover(); - stateManager.endSelectCells(); - + const isHasSelected = !!stateManager.select.ranges?.length; // 点击空白区域取消选中 if (table.options.select?.blankAreaClickDeselect ?? true) { eventManager.dealTableSelect(); } + stateManager.endSelectCells(true, isHasSelected); stateManager.updateCursor(); table.scenegraph.updateChartState(null); diff --git a/packages/vtable/src/event/media-click.ts b/packages/vtable/src/event/media-click.ts index 334796e5b..4a5c498eb 100644 --- a/packages/vtable/src/event/media-click.ts +++ b/packages/vtable/src/event/media-click.ts @@ -12,6 +12,11 @@ export function bindMediaClick(table: BaseTableAPI): void { table.on(TABLE_EVENT_TYPE.CLICK_CELL, (e: MousePointerCellEvent) => { //如果目前是在某个icon上,如收起展开按钮 则不进行其他点击逻辑 const { col, row } = e; + + if (e.target.type === 'image' && (e.target as any).role && (e.target as any).role.startsWith('icon')) { + // click icon + return; + } let cellType; if (table.internalProps.layoutMap.isHeader(col, row)) { cellType = table.isPivotTable() diff --git a/packages/vtable/src/index.ts b/packages/vtable/src/index.ts index e0bd280d9..2928eca9e 100644 --- a/packages/vtable/src/index.ts +++ b/packages/vtable/src/index.ts @@ -50,7 +50,8 @@ export { getDataCellPath } from './tools/get-data-path'; export * from './render/jsx'; export { getTargetCell } from './event/util'; -export * as VRender from './vrender'; +// export * as VRender from './vrender'; +import * as VRender from './vrender'; export const version = __VERSION__; /** @@ -103,7 +104,8 @@ export { renderChart, graphicUtil, setCustomAlphabetCharSet, - restoreMeasureText + restoreMeasureText, + VRender }; /** @private */ diff --git a/packages/vtable/src/layout/layout-helper.ts b/packages/vtable/src/layout/layout-helper.ts index 8a951c456..4a655d3a8 100644 --- a/packages/vtable/src/layout/layout-helper.ts +++ b/packages/vtable/src/layout/layout-helper.ts @@ -17,6 +17,7 @@ import type { IImageColumnIndicator, IImageHeaderIndicator } from '../ts-types/p import type { IImageColumnBodyDefine, IImageHeaderDefine } from '../ts-types/list-table/define/image-define'; import type { ITreeLayoutHeadNode } from './tree-helper'; import { DimensionTree } from './tree-helper'; +import type { ISparklineColumnIndicator } from '../ts-types/pivot-table/indicator/sparkline-indicator'; export function checkHasAggregation(layoutMap: SimpleHeaderLayoutMap) { const columnObjects = layoutMap.columnObjects; @@ -242,13 +243,22 @@ export function parseColKeyRowKeyForPivotTable(table: PivotTable, options: Pivot keys.push(indicatorObj); } else { keys.push(indicatorObj.indicatorKey); - if ((indicatorObj as IChartColumnIndicator).chartSpec) { + if ( + (indicatorObj as IChartColumnIndicator).chartSpec || + (indicatorObj as ISparklineColumnIndicator).sparklineSpec + ) { if (table.internalProps.dataConfig?.aggregationRules) { - table.internalProps.dataConfig?.aggregationRules.push({ - field: indicatorObj.indicatorKey, - indicatorKey: indicatorObj.indicatorKey, - aggregationType: AggregationType.NONE - }); + if ( + !table.internalProps.dataConfig.aggregationRules.find(aggregation => { + return aggregation.indicatorKey === indicatorObj.indicatorKey; + }) + ) { + table.internalProps.dataConfig.aggregationRules.push({ + field: indicatorObj.indicatorKey, + indicatorKey: indicatorObj.indicatorKey, + aggregationType: AggregationType.NONE + }); + } } else if (table.internalProps.dataConfig) { table.internalProps.dataConfig.aggregationRules = [ { diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index e71093b8a..09c3f44aa 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -276,74 +276,73 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { } this.sharedVar.seqId = Math.max(this.sharedVar.seqId, this._headerObjects.length); - //生成cornerHeaderObjs及_cornerHeaderCellIds - if (this.cornerSetting.titleOnDimension === 'column') { - let colDimensionKeys = this.columnDimensionTree.dimensionKeysIncludeVirtual.valueArr(); + //#region 生成cornerHeaderObjs及_cornerHeaderCellIds + // if (this.cornerSetting.titleOnDimension === 'all') { + let colDimensionKeys = this.columnDimensionTree.dimensionKeysIncludeVirtual.valueArr(); + //#region 处理需求 当没有数据时仍然显示角头维度名称 + if ( + this.dataset && + (this.dataset.records?.length ?? 0) === 0 && + !this.dataset.customColTree && + !this.dataset.customRowTree + ) { + colDimensionKeys = this.columnsDefine.map(define => { + if (typeof define === 'string') { + return define; + } + return define.dimensionKey; + }); + if (this.indicatorsAsCol) { + colDimensionKeys.push(this.indicatorDimensionKey); + } + } + //#endregion + + colDimensionKeys = this.columnHeaderTitle ? [''].concat(colDimensionKeys) : colDimensionKeys; + + let rowDimensionKeys: string[]; + let extensionRowDimensions = []; + if (this.rowHierarchyType === 'tree' && this.extensionRows?.length >= 1) { + // 如果是有扩展行维度 + const rowTreeFirstKey = []; + rowTreeFirstKey.push(this.rowDimensionKeys[0]); + this._extensionRowDimensionKeys.forEach(extensionRowKeys => { + rowTreeFirstKey.push(extensionRowKeys[0]); + }); + extensionRowDimensions = this.extensionRows.reduce((dimensions, cur) => { + return dimensions.concat(cur.rows); + }, []); + + rowDimensionKeys = this.rowHeaderTitle ? [''].concat(rowTreeFirstKey as any) : rowTreeFirstKey; + } else { //#region 处理需求 当没有数据时仍然显示角头维度名称 + rowDimensionKeys = this.rowDimensionTree.dimensionKeysIncludeVirtual.valueArr(); if ( this.dataset && (this.dataset.records?.length ?? 0) === 0 && !this.dataset.customColTree && !this.dataset.customRowTree ) { - colDimensionKeys = this.columnsDefine.map(define => { + rowDimensionKeys = this.rowsDefine.map(define => { if (typeof define === 'string') { return define; } return define.dimensionKey; }); - if (this.indicatorsAsCol) { - colDimensionKeys.push(this.indicatorDimensionKey); + if (!this.indicatorsAsCol) { + rowDimensionKeys.push(this.indicatorDimensionKey); } } //#endregion - this.cornerHeaderObjs = this._addCornerHeaders( - this.columnHeaderTitle ? [''].concat(colDimensionKeys) : colDimensionKeys, - this.columnsDefine - ); - } else if (this.cornerSetting.titleOnDimension === 'row') { - if (this.rowHierarchyType === 'tree' && this.extensionRows?.length >= 1) { - // 如果是有扩展行维度 - const rowTreeFirstKey = []; - rowTreeFirstKey.push(this.rowDimensionKeys[0]); - this._extensionRowDimensionKeys.forEach(extensionRowKeys => { - rowTreeFirstKey.push(extensionRowKeys[0]); - }); - const extensionRowDimensions = this.extensionRows.reduce((dimensions, cur) => { - return dimensions.concat(cur.rows); - }, []); - this.cornerHeaderObjs = this._addCornerHeaders( - this.rowHeaderTitle ? [''].concat(rowTreeFirstKey as any) : rowTreeFirstKey, - this.rowsDefine.concat(extensionRowDimensions) - ); - } else { - //#region 处理需求 当没有数据时仍然显示角头维度名称 - let rowDimensionKeys = this.rowDimensionTree.dimensionKeysIncludeVirtual.valueArr(); - if ( - this.dataset && - (this.dataset.records?.length ?? 0) === 0 && - !this.dataset.customColTree && - !this.dataset.customRowTree - ) { - rowDimensionKeys = this.rowsDefine.map(define => { - if (typeof define === 'string') { - return define; - } - return define.dimensionKey; - }); - if (!this.indicatorsAsCol) { - rowDimensionKeys.push(this.indicatorDimensionKey); - } - } - //#endregion - this.cornerHeaderObjs = this._addCornerHeaders( - this.rowHeaderTitle ? [''].concat(rowDimensionKeys) : rowDimensionKeys, - this.rowsDefine - ); - } - } else { - this.cornerHeaderObjs = this._addCornerHeaders(null, undefined); + rowDimensionKeys = this.rowHeaderTitle ? [''].concat(rowDimensionKeys) : rowDimensionKeys; } + + this.cornerHeaderObjs = this._addCornerHeaders( + colDimensionKeys, + rowDimensionKeys, + this.columnsDefine.concat(...this.rowsDefine, ...extensionRowDimensions) + ); + //#endregion this.colIndex = 0; this._headerObjectMap = this._headerObjects.reduce((o, e) => { o[e.id as number] = e; @@ -399,35 +398,6 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { // } return false; }); - - // if (this.indicatorsAsCol) { - // const cell_id = 'rowHeaderEmpty'; - // this._headerObjectMap[cell_id] = { - // id: cell_id, - // title: '', - // field: cell_id, - // headerType: this.cornerSetting.headerType ?? 'text', - // style: this.cornerSetting.headerStyle, - // define: { - // // id: - // } - // }; - // this._headerObjects.push(this._headerObjectMap[cell_id]); - // // this.rowShowAttrs.push(cell_id); - - // // deal with sub indicator axis - - // if (!this.hasTwoIndicatorAxes) { - // // this.colShowAttrs.pop(); - // } - // } else { - // const axisOption = ((this._table as PivotChart).pivotChartAxes as ITableAxisOption[]).find(axisOption => { - // return axisOption.orient === 'left'; - // }); - // if (axisOption?.visible === false) { - // // this.rowShowAttrs.pop(); - // } - // } } this.handleRowSeriesNumber(table.internalProps.rowSeriesNumber); @@ -667,73 +637,290 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { dealHeaderForTreeMode(hd, _headerCellIds, results, roots, row, totalLevel, show, dimensions, this); } } - private _addCornerHeaders(dimensionKeys: string[] | null, dimensions: (string | IDimension)[]) { + private _addCornerHeaders( + colDimensionKeys: string[] | null, + rowDimensionKeys: string[] | null, + dimensions: (string | IDimension)[] + ) { const results: HeaderData[] = []; - if (dimensionKeys) { - dimensionKeys.forEach((dimensionKey: string, key: number) => { - const id = ++this.sharedVar.seqId; - // const dimensionInfo: IDimension = - // (this.rowsDefine?.find(dimension => - // typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey - // ) as IDimension) ?? - // (this.columnsDefine?.find(dimension => - // typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey - // ) as IDimension); - const dimensionInfo: IDimension = dimensions.find(dimension => - typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey - ) as IDimension; - const cell: HeaderData = { - id, - title: - dimensionKey === this.indicatorDimensionKey - ? this.indicatorTitle - : dimensionInfo - ? dimensionInfo.title - : dimensionKey === 'axis' - ? '' - : (dimensionKey as string), - field: dimensionKey, //'维度名称', - style: this.cornerSetting.headerStyle, - headerType: this.cornerSetting.headerType ?? 'text', - showSort: dimensionInfo?.showSortInCorner, - sort: dimensionInfo?.sort, - define: { + if (this.cornerSetting.titleOnDimension === 'all') { + if (this.indicatorsAsCol) { + let indicatorAtIndex = -1; + if (colDimensionKeys) { + colDimensionKeys.forEach((dimensionKey: string, key: number) => { + if (dimensionKey === this.indicatorDimensionKey) { + indicatorAtIndex = key; + } + const id = ++this.sharedVar.seqId; + const dimensionInfo: IDimension = dimensions.find(dimension => + typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey + ) as IDimension; + const cell: HeaderData = { + id, + title: + dimensionKey === this.indicatorDimensionKey + ? this.indicatorTitle + : dimensionInfo + ? dimensionInfo.title + : dimensionKey === 'axis' + ? '' + : (dimensionKey as string), + field: dimensionKey, //'维度名称', + style: this.cornerSetting.headerStyle, + headerType: this.cornerSetting.headerType ?? 'text', + showSort: dimensionInfo?.showSortInCorner, + sort: dimensionInfo?.sort, + define: { + showSort: dimensionInfo?.showSortInCorner, + sort: dimensionInfo?.sort, + dimensionKey: dimensionKey, // '维度名称', + id, + value: dimensionKey, + headerEditor: this.cornerSetting.headerEditor, + disableHeaderHover: !!this.cornerSetting.disableHeaderHover, + disableHeaderSelect: !!this.cornerSetting.disableHeaderSelect + }, + dropDownMenu: dimensionInfo?.cornerDropDownMenu, + pivotInfo: { + value: dimensionInfo?.title ?? '', + dimensionKey, + isPivotCorner: true + // customInfo: dimensionInfo?.customInfo + }, + description: dimensionInfo?.cornerDescription + }; + results[id] = cell; + this._headerObjects[id] = cell; + + if (!this._cornerHeaderCellFullPathIds[key]) { + this._cornerHeaderCellFullPathIds[key] = []; + } + for (let r = 0; r < this.rowHeaderLevelCount; r++) { + this._cornerHeaderCellFullPathIds[key][r] = id; + } + }); + } + if (rowDimensionKeys) { + rowDimensionKeys.forEach((dimensionKey: string, key: number) => { + const id = ++this.sharedVar.seqId; + const dimensionInfo: IDimension = dimensions.find(dimension => + typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey + ) as IDimension; + const cell: HeaderData = { + id, + title: + dimensionKey === this.indicatorDimensionKey + ? this.indicatorTitle + : dimensionInfo + ? dimensionInfo.title + : dimensionKey === 'axis' + ? '' + : (dimensionKey as string), + field: dimensionKey, //'维度名称', + style: this.cornerSetting.headerStyle, + headerType: this.cornerSetting.headerType ?? 'text', + showSort: dimensionInfo?.showSortInCorner, + sort: dimensionInfo?.sort, + define: { + showSort: dimensionInfo?.showSortInCorner, + sort: dimensionInfo?.sort, + dimensionKey: dimensionKey, // '维度名称', + id, + value: dimensionKey, + headerEditor: this.cornerSetting.headerEditor, + disableHeaderHover: !!this.cornerSetting.disableHeaderHover, + disableHeaderSelect: !!this.cornerSetting.disableHeaderSelect + }, + dropDownMenu: dimensionInfo?.cornerDropDownMenu, + pivotInfo: { + value: dimensionInfo?.title ?? '', + dimensionKey, + isPivotCorner: true + // customInfo: dimensionInfo?.customInfo + }, + description: dimensionInfo?.cornerDescription + }; + results[id] = cell; + this._headerObjects[id] = cell; + if (!this._cornerHeaderCellFullPathIds[indicatorAtIndex]) { + this._cornerHeaderCellFullPathIds[indicatorAtIndex] = []; + } + this._cornerHeaderCellFullPathIds[indicatorAtIndex][key] = id; + }); + } + } else { + let indicatorAtIndex = -1; + if (rowDimensionKeys) { + rowDimensionKeys.forEach((dimensionKey: string, key: number) => { + if (dimensionKey === this.indicatorDimensionKey) { + indicatorAtIndex = key; + } + const id = ++this.sharedVar.seqId; + const dimensionInfo: IDimension = dimensions.find(dimension => + typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey + ) as IDimension; + const cell: HeaderData = { + id, + title: + dimensionKey === this.indicatorDimensionKey + ? this.indicatorTitle + : dimensionInfo + ? dimensionInfo.title + : dimensionKey === 'axis' + ? '' + : (dimensionKey as string), + field: dimensionKey, //'维度名称', + style: this.cornerSetting.headerStyle, + headerType: this.cornerSetting.headerType ?? 'text', + showSort: dimensionInfo?.showSortInCorner, + sort: dimensionInfo?.sort, + define: { + showSort: dimensionInfo?.showSortInCorner, + sort: dimensionInfo?.sort, + dimensionKey: dimensionKey, // '维度名称', + id, + value: dimensionKey, + headerEditor: this.cornerSetting.headerEditor, + disableHeaderHover: !!this.cornerSetting.disableHeaderHover, + disableHeaderSelect: !!this.cornerSetting.disableHeaderSelect + }, + dropDownMenu: dimensionInfo?.cornerDropDownMenu, + pivotInfo: { + value: dimensionInfo?.title ?? '', + dimensionKey, + isPivotCorner: true + // customInfo: dimensionInfo?.customInfo + }, + description: dimensionInfo?.cornerDescription + }; + results[id] = cell; + this._headerObjects[id] = cell; + + for (let r = 0; r < this.columnHeaderLevelCount; r++) { + if (!this._cornerHeaderCellFullPathIds[r]) { + this._cornerHeaderCellFullPathIds[r] = []; + } + this._cornerHeaderCellFullPathIds[r][key] = id; + } + }); + } + if (colDimensionKeys) { + colDimensionKeys.forEach((dimensionKey: string, key: number) => { + const id = ++this.sharedVar.seqId; + const dimensionInfo: IDimension = dimensions.find(dimension => + typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey + ) as IDimension; + const cell: HeaderData = { + id, + title: + dimensionKey === this.indicatorDimensionKey + ? this.indicatorTitle + : dimensionInfo + ? dimensionInfo.title + : dimensionKey === 'axis' + ? '' + : (dimensionKey as string), + field: dimensionKey, //'维度名称', + style: this.cornerSetting.headerStyle, + headerType: this.cornerSetting.headerType ?? 'text', + showSort: dimensionInfo?.showSortInCorner, + sort: dimensionInfo?.sort, + define: { + showSort: dimensionInfo?.showSortInCorner, + sort: dimensionInfo?.sort, + dimensionKey: dimensionKey, // '维度名称', + id, + value: dimensionKey, + headerEditor: this.cornerSetting.headerEditor, + disableHeaderHover: !!this.cornerSetting.disableHeaderHover, + disableHeaderSelect: !!this.cornerSetting.disableHeaderSelect + }, + dropDownMenu: dimensionInfo?.cornerDropDownMenu, + pivotInfo: { + value: dimensionInfo?.title ?? '', + dimensionKey, + isPivotCorner: true + // customInfo: dimensionInfo?.customInfo + }, + description: dimensionInfo?.cornerDescription + }; + results[id] = cell; + this._headerObjects[id] = cell; + // if (!this._cornerHeaderCellFullPathIds[indicatorAtIndex]) { + // this._cornerHeaderCellFullPathIds[indicatorAtIndex] = []; + // } + this._cornerHeaderCellFullPathIds[key][indicatorAtIndex] = id; + }); + } + } + } else if (this.cornerSetting.titleOnDimension === 'row' || this.cornerSetting.titleOnDimension === 'column') { + const dimensionKeys = this.cornerSetting?.titleOnDimension === 'row' ? rowDimensionKeys : colDimensionKeys; + if (dimensionKeys) { + dimensionKeys.forEach((dimensionKey: string, key: number) => { + const id = ++this.sharedVar.seqId; + // const dimensionInfo: IDimension = + // (this.rowsDefine?.find(dimension => + // typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey + // ) as IDimension) ?? + // (this.columnsDefine?.find(dimension => + // typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey + // ) as IDimension); + const dimensionInfo: IDimension = dimensions.find(dimension => + typeof dimension === 'string' ? false : dimension.dimensionKey === dimensionKey + ) as IDimension; + const cell: HeaderData = { + id, + title: + dimensionKey === this.indicatorDimensionKey + ? this.indicatorTitle + : dimensionInfo + ? dimensionInfo.title + : dimensionKey === 'axis' + ? '' + : (dimensionKey as string), + field: dimensionKey, //'维度名称', + style: this.cornerSetting.headerStyle, + headerType: this.cornerSetting.headerType ?? 'text', showSort: dimensionInfo?.showSortInCorner, sort: dimensionInfo?.sort, - dimensionKey: dimensionKey, // '维度名称', - id, - value: dimensionKey, - headerEditor: this.cornerSetting.headerEditor, - disableHeaderHover: !!this.cornerSetting.disableHeaderHover, - disableHeaderSelect: !!this.cornerSetting.disableHeaderSelect - }, - dropDownMenu: dimensionInfo?.cornerDropDownMenu, - pivotInfo: { - value: dimensionInfo?.title ?? '', - dimensionKey, - isPivotCorner: true - // customInfo: dimensionInfo?.customInfo - }, - description: dimensionInfo?.cornerDescription - }; - results[id] = cell; - this._headerObjects[id] = cell; - if (this.cornerSetting.titleOnDimension === 'column') { - if (!this._cornerHeaderCellFullPathIds[key]) { - this._cornerHeaderCellFullPathIds[key] = []; - } - for (let r = 0; r < this.rowHeaderLevelCount; r++) { - this._cornerHeaderCellFullPathIds[key][r] = id; - } - } else if (this.cornerSetting.titleOnDimension === 'row') { - for (let r = 0; r < this.columnHeaderLevelCount; r++) { - if (!this._cornerHeaderCellFullPathIds[r]) { - this._cornerHeaderCellFullPathIds[r] = []; + define: { + showSort: dimensionInfo?.showSortInCorner, + sort: dimensionInfo?.sort, + dimensionKey: dimensionKey, // '维度名称', + id, + value: dimensionKey, + headerEditor: this.cornerSetting.headerEditor, + disableHeaderHover: !!this.cornerSetting.disableHeaderHover, + disableHeaderSelect: !!this.cornerSetting.disableHeaderSelect + }, + dropDownMenu: dimensionInfo?.cornerDropDownMenu, + pivotInfo: { + value: dimensionInfo?.title ?? '', + dimensionKey, + isPivotCorner: true + // customInfo: dimensionInfo?.customInfo + }, + description: dimensionInfo?.cornerDescription + }; + results[id] = cell; + this._headerObjects[id] = cell; + if (this.cornerSetting.titleOnDimension === 'column') { + if (!this._cornerHeaderCellFullPathIds[key]) { + this._cornerHeaderCellFullPathIds[key] = []; + } + for (let r = 0; r < this.rowHeaderLevelCount; r++) { + this._cornerHeaderCellFullPathIds[key][r] = id; + } + } else if (this.cornerSetting.titleOnDimension === 'row') { + for (let r = 0; r < this.columnHeaderLevelCount; r++) { + if (!this._cornerHeaderCellFullPathIds[r]) { + this._cornerHeaderCellFullPathIds[r] = []; + } + this._cornerHeaderCellFullPathIds[r][key] = id; } - this._cornerHeaderCellFullPathIds[r][key] = id; } - } - }); + }); + } } else { const id = ++this.sharedVar.seqId; const cell: HeaderData = { @@ -761,6 +948,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { } } } + return results; } private generateExtensionRowTree() { @@ -1215,7 +1403,10 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { if (count === 0 && this.dataset && !this.dataset.customColTree && !this.dataset.customRowTree) { if (this.cornerSetting.titleOnDimension === 'row') { count = 1; - } else if ((this.dataset.records?.length ?? 0) === 0 && this.cornerSetting.titleOnDimension === 'column') { + } else if ( + (this.dataset.records?.length ?? 0) === 0 && + (this.cornerSetting.titleOnDimension === 'column' || this.cornerSetting.titleOnDimension === 'all') + ) { count = this.columnsDefine.length ?? 0; } } else if ( @@ -1224,7 +1415,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { !this.dataset.customColTree && !this.dataset.customRowTree ) { - if (this.cornerSetting.titleOnDimension === 'column') { + if (this.cornerSetting.titleOnDimension === 'column' || this.cornerSetting.titleOnDimension === 'all') { count = this.columnsDefine.length ?? 0; if (!this.hideIndicatorName && this.indicatorsAsCol) { count++; @@ -1279,7 +1470,10 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { if (count === 0 && this.dataset && !this.dataset.customColTree && !this.dataset.customRowTree) { if (this.cornerSetting.titleOnDimension === 'column') { count = 1; - } else if ((this.dataset.records?.length ?? 0) === 0 && this.cornerSetting.titleOnDimension === 'row') { + } else if ( + (this.dataset.records?.length ?? 0) === 0 && + (this.cornerSetting.titleOnDimension === 'row' || this.cornerSetting.titleOnDimension === 'all') + ) { count = this.rowsDefine.length ?? 0; } } else if ( @@ -1288,7 +1482,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { !this.dataset.customColTree && !this.dataset.customRowTree ) { - if (this.cornerSetting.titleOnDimension === 'row') { + if (this.cornerSetting.titleOnDimension === 'row' || this.cornerSetting.titleOnDimension === 'all') { count = this.rowsDefine.length; if (!this.hideIndicatorName && !this.indicatorsAsCol) { count++; diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 744a08d66..5df2a9d98 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -37,9 +37,12 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { private seqId: number = 0; private _headerObjects: HeaderData[]; private _headerObjectMap: { [key in LayoutObjectId]: HeaderData }; + private _headerObjectsIncludeHided: HeaderData[]; + // private _headerObjectMapIncludeHided: { [key in LayoutObjectId]: HeaderData }; // private _headerObjectFieldKey: { [key in string]: HeaderData }; private _headerCellIds: number[][]; private _columns: ColumnData[]; + private _columnsIncludeHided: ColumnData[]; rowSeriesNumberColumn: SeriesNumberColumnData[]; leftRowSeriesNumberColumn: SeriesNumberColumnData[]; rightRowSeriesNumberColumn: SeriesNumberColumnData[]; @@ -69,11 +72,20 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { this._showHeader = showHeader; this._table = table; this._columns = []; + this._columnsIncludeHided = []; this._headerCellIds = []; this.hierarchyIndent = hierarchyIndent ?? 20; this.hierarchyTextStartAlignment = table.options.hierarchyTextStartAlignment; this.columnTree = new DimensionTree(columns as any, { seqId: 0 }); //seqId这里没有利用上 所有顺便传了0 - this._headerObjects = this._addHeaders(0, columns, []); + this._headerObjectsIncludeHided = this._addHeaders(0, columns, []); + // this._headerObjectMapIncludeHided = this._headerObjectsIncludeHided.reduce((o, e) => { + // o[e.id as number] = e; + // return o; + // }, {} as { [key in LayoutObjectId]: HeaderData }); + + this._headerObjects = this._headerObjectsIncludeHided.filter(col => { + return col.define.hide !== true; + }); this._headerObjectMap = this._headerObjects.reduce((o, e) => { o[e.id as number] = e; return o; @@ -729,6 +741,9 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { get columnObjects(): ColumnData[] { return this._columns; } + get headerObjectsIncludeHided(): HeaderData[] { + return this._headerObjectsIncludeHided; + } //对比multi-layout 那个里面有columWidths对象,保持结构一致 get columnWidths(): WidthData[] { if (this.leftRowSeriesNumberColumnCount) { @@ -1114,29 +1129,33 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { c => results.push(c) ); } else { - const colDef = hd; - this._columns.push({ + const colDef = { id: this.seqId++, - field: colDef.field, + field: hd.field, // fieldKey: colDef.fieldKey, - fieldFormat: colDef.fieldFormat, - width: colDef.width, - minWidth: colDef.minWidth, - maxWidth: colDef.maxWidth, - icon: colDef.icon, - cellType: colDef.cellType ?? (colDef as any).columnType ?? 'text', - chartModule: 'chartModule' in colDef ? colDef.chartModule : null, // todo: 放到对应的column对象中 - chartSpec: 'chartSpec' in colDef ? colDef.chartSpec : null, // todo: 放到对应的column对象中 - sparklineSpec: 'sparklineSpec' in colDef ? colDef.sparklineSpec : DefaultSparklineSpec, // todo: 放到对应的column对象中 - style: colDef.style, - define: colDef, - columnWidthComputeMode: colDef.columnWidthComputeMode, - disableColumnResize: colDef?.disableColumnResize, - aggregation: this._getAggregationForColumn(colDef, col), + fieldFormat: hd.fieldFormat, + width: hd.width, + minWidth: hd.minWidth, + maxWidth: hd.maxWidth, + icon: hd.icon, + cellType: hd.cellType ?? (hd as any).columnType ?? 'text', + chartModule: 'chartModule' in hd ? hd.chartModule : null, // todo: 放到对应的column对象中 + chartSpec: 'chartSpec' in hd ? hd.chartSpec : null, // todo: 放到对应的column对象中 + sparklineSpec: 'sparklineSpec' in hd ? hd.sparklineSpec : DefaultSparklineSpec, // todo: 放到对应的column对象中 + style: hd.style, + define: hd, + columnWidthComputeMode: hd.columnWidthComputeMode, + disableColumnResize: hd?.disableColumnResize, + aggregation: this._getAggregationForColumn(hd, col), isChildNode: row >= 1 - }); - for (let r = row + 1; r < this._headerCellIds.length; r++) { - this._headerCellIds[r][col] = id; + }; + this._columnsIncludeHided.push(colDef); + if (hd.hide !== true) { + this._columns.push(colDef); + + for (let r = row + 1; r < this._headerCellIds.length; r++) { + this._headerCellIds[r][col] = id; + } } } }); diff --git a/packages/vtable/src/scenegraph/component/custom.ts b/packages/vtable/src/scenegraph/component/custom.ts index 8548f837f..38b484ea2 100644 --- a/packages/vtable/src/scenegraph/component/custom.ts +++ b/packages/vtable/src/scenegraph/component/custom.ts @@ -519,6 +519,9 @@ function bindAttributeUpdate(group: VGroup, col: number, row: number, index: num function onBeforeAttributeUpdate(val: Record, attribute: any) { // @ts-ignore const graphic = this as any; + if (graphic.skipMergeUpdate) { + return; + } const cellGroup = getTargetCell(graphic) as Group; const table = ((cellGroup as any).stage as any).table as BaseTableAPI; graphic.skipAttributeUpdate = true; @@ -535,7 +538,8 @@ function onBeforeAttributeUpdate(val: Record, attribute: any) { if (col === cellGroup.col && row === cellGroup.row) { continue; } - const cell = table.scenegraph.getCell(col, row); + // const cell = table.scenegraph.getCell(col, row); + const cell = table.scenegraph.highPerformanceGetCell(col, row); if (cell.role === 'cell') { const target = cell.getChildByName(graphic.name, true); if (!target || target.skipAttributeUpdate) { diff --git a/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts b/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts index c21c8aca4..33699feb9 100644 --- a/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts +++ b/packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts @@ -156,21 +156,21 @@ export class SplitGroupAfterRenderContribution implements IGroupRenderContributi return; } const bottomRight = table?.theme.cellBorderClipDirection === 'bottom-right'; - let deltaWidth = 0; - let deltaHeight = 0; + // let deltaWidth = 0; + // let deltaHeight = 0; if (bottomRight) { x = Math.floor(x) - 0.5; y = Math.floor(y) - 0.5; - if (group.role === 'cell') { - const col = (group as any).col as number; - const row = (group as any).row as number; - if (table && col === table.colCount - 1) { - deltaWidth = 1; - } - if (table && row === table.rowCount - 1) { - deltaHeight = 1; - } - } + // if (group.role === 'cell') { + // const col = (group as any).col as number; + // const row = (group as any).row as number; + // if (table && col === table.colCount - 1) { + // deltaWidth = 1; + // } + // if (table && row === table.rowCount - 1) { + // deltaHeight = 1; + // } + // } } else { x = Math.floor(x) + 0.5; y = Math.floor(y) + 0.5; @@ -182,8 +182,9 @@ export class SplitGroupAfterRenderContribution implements IGroupRenderContributi } const { width: widthFroDraw, height: heightFroDraw } = getCellSizeForDraw( group, - Math.ceil(width + deltaWidth), - Math.ceil(height + deltaHeight) + Math.ceil(width), + Math.ceil(height), + bottomRight ); widthForStroke = widthFroDraw; heightForStroke = heightFroDraw; @@ -267,8 +268,9 @@ export function renderStroke( context.setStrokeStyle(group, group.attribute, x, y, groupAttribute); // if (isHighlight) { // context.setLineDash(highlightDash); - // context.lineCap = 'butt'; // } + // const oldLineCap = context.lineCap; + // context.lineCap = 'square'; const { lineDash = groupAttribute.lineDash } = group.attribute as any; // const lineDash = context.getLineDash(); @@ -280,16 +282,23 @@ export function renderStroke( context.beginPath(); context.moveTo(x, y); + const strokeTop = (isStrokeTrue || stroke[0]) && (isWidthNumber || strokeArrayWidth[0]); + const strokeRight = (isStrokeTrue || stroke[1]) && (isWidthNumber || strokeArrayWidth[1]); + const strokeBottom = (isStrokeTrue || stroke[2]) && (isWidthNumber || strokeArrayWidth[2]); + const strokeLeft = (isStrokeTrue || stroke[3]) && (isWidthNumber || strokeArrayWidth[3]); + // top - if ((isStrokeTrue || stroke[0]) && (isWidthNumber || strokeArrayWidth[0])) { + if (strokeTop) { // context.lineTo(x + width, y); + const deltaLeft = (isWidthNumber ? widthInfo.width : strokeArrayWidth[0]) / 2; + const deltaRight = (isWidthNumber ? widthInfo.width : strokeArrayWidth[0]) / 2; if (isPart && Array.isArray(part[0])) { - context.moveTo(x + width * part[0][0], y); - context.lineTo(x + width * (part[0][1] - part[0][0]), y); - context.moveTo(x + width, y); + context.moveTo(x - deltaLeft + (width + deltaLeft + deltaRight) * part[0][0], y); + context.lineTo(x - deltaLeft + (width + deltaLeft + deltaRight) * (part[0][1] - part[0][0]), y); + context.moveTo(x + width + deltaRight, y); } else { - context.moveTo(x, y); - context.lineTo(x + width, y); + context.moveTo(x - deltaLeft, y); + context.lineTo(x + width + deltaRight, y); } if (isSplitDraw || isDash) { if (strokeArrayColor && strokeArrayColor[0]) { @@ -312,15 +321,17 @@ export function renderStroke( context.moveTo(x + width, y); } // right - if ((isStrokeTrue || stroke[1]) && (isWidthNumber || strokeArrayWidth[1])) { + if (strokeRight) { // context.lineTo(x + width, y + height); + const deltaTop = (isWidthNumber ? widthInfo.width : strokeArrayWidth[1]) / 2; + const deltaBottom = (isWidthNumber ? widthInfo.width : strokeArrayWidth[1]) / 2; if (isPart && Array.isArray(part[1])) { - context.moveTo(x + width, y + height * part[1][0]); - context.lineTo(x + width, y + height * (part[1][1] - part[1][0])); - context.moveTo(x + width, y + height); + context.moveTo(x + width, y - deltaTop + height * part[1][0]); + context.lineTo(x + width, y - deltaTop + (height + deltaTop + deltaBottom) * (part[1][1] - part[1][0])); + context.moveTo(x + width, y + height + deltaBottom); } else { - context.moveTo(x + width, y); - context.lineTo(x + width, y + height); + context.moveTo(x + width, y - deltaTop); + context.lineTo(x + width, y + height + deltaBottom); } if (isSplitDraw || isDash) { if (strokeArrayColor && strokeArrayColor[1]) { @@ -343,15 +354,17 @@ export function renderStroke( context.moveTo(x + width, y + height); } // bottom - if ((isStrokeTrue || stroke[2]) && (isWidthNumber || strokeArrayWidth[2])) { + if (strokeBottom) { // context.lineTo(x, y + height); + const deltaLeft = (isWidthNumber ? widthInfo.width : strokeArrayWidth[2]) / 2; + const deltaRight = (isWidthNumber ? widthInfo.width : strokeArrayWidth[2]) / 2; if (isPart && Array.isArray(part[2])) { - context.moveTo(x + width * part[2][0], y + height); - context.lineTo(x + width * (part[2][1] - part[2][0]), y + height); - context.moveTo(x, y + height); + context.moveTo(x - deltaLeft + (width + deltaLeft + deltaRight) * part[2][0], y + height); + context.lineTo(x - deltaLeft + (width + deltaLeft + deltaRight) * (part[2][1] - part[2][0]), y + height); + context.moveTo(x - deltaLeft, y + height); } else { - context.moveTo(x, y + height); - context.lineTo(x + width, y + height); + context.moveTo(x - deltaLeft, y + height); + context.lineTo(x + width + deltaRight, y + height); } if (isSplitDraw || isDash) { if (strokeArrayColor && strokeArrayColor[2]) { @@ -374,15 +387,17 @@ export function renderStroke( context.moveTo(x, y + height); } // left - if ((isStrokeTrue || stroke[3]) && (isWidthNumber || strokeArrayWidth[3])) { + if (strokeLeft) { // context.lineTo(x, y); + const deltaTop = (isWidthNumber ? widthInfo.width : strokeArrayWidth[3]) / 2; + const deltaBottom = (isWidthNumber ? widthInfo.width : strokeArrayWidth[3]) / 2; if (isPart && Array.isArray(part[3])) { - context.moveTo(x, y + height * part[3][0]); - context.lineTo(x, y + height * (part[3][1] - part[3][0])); - context.moveTo(x, y); + context.moveTo(x, y - deltaTop + (height + deltaTop + deltaBottom) * part[3][0]); + context.lineTo(x, y - deltaTop + (height + deltaTop + deltaBottom) * (part[3][1] - part[3][0])); + context.moveTo(x, y - deltaTop); } else { - context.moveTo(x, y); - context.lineTo(x, y + height); + context.moveTo(x, y - deltaTop); + context.lineTo(x, y + height + deltaBottom); } if (isSplitDraw || isDash) { if (strokeArrayColor && strokeArrayColor[3]) { @@ -413,6 +428,7 @@ export function renderStroke( context.stroke(); } context.lineDashOffset = 0; + // context.lineCap = oldLineCap; context.setLineDash([]); } @@ -531,21 +547,21 @@ export class DashGroupAfterRenderContribution implements IGroupRenderContributio let heightForStroke; if (lineWidth & 1) { const bottomRight = table.theme.cellBorderClipDirection === 'bottom-right'; - let deltaWidth = 0; - let deltaHeight = 0; + const deltaWidth = 0; + const deltaHeight = 0; if (bottomRight) { x = Math.floor(x) - 0.5; y = Math.floor(y) - 0.5; - if (group.role === 'cell') { - const col = (group as any).col as number; - const row = (group as any).row as number; - if (table && col === table.colCount - 1) { - deltaWidth = 1; - } - if (table && row === table.rowCount - 1) { - deltaHeight = 1; - } - } + // if (group.role === 'cell') { + // const col = (group as any).col as number; + // const row = (group as any).row as number; + // if (table && col === table.colCount - 1) { + // deltaWidth = 1; + // } + // if (table && row === table.rowCount - 1) { + // deltaHeight = 1; + // } + // } } else { x = Math.floor(x) + 0.5; y = Math.floor(y) + 0.5; @@ -554,7 +570,8 @@ export class DashGroupAfterRenderContribution implements IGroupRenderContributio const { width: widthFroDraw, height: heightFroDraw } = getCellSizeForDraw( group, Math.ceil(width + deltaWidth), - Math.ceil(height + deltaHeight) + Math.ceil(height + deltaHeight), + bottomRight ); widthForStroke = widthFroDraw; heightForStroke = heightFroDraw; @@ -736,34 +753,37 @@ export class AdjustPosGroupAfterRenderContribution implements IGroupRenderContri width = Math.round(width); height = Math.round(height); } - const { width: widthFroDraw, height: heightFroDraw } = getCellSizeForDraw( - group, - Math.ceil(width), - Math.ceil(height) - ); + context.beginPath(); const bottomRight = table?.theme.cellBorderClipDirection === 'bottom-right'; - let deltaWidth = 0; - let deltaHeight = 0; + const deltaWidth = 0; + const deltaHeight = 0; if (bottomRight) { x = Math.floor(x) - 0.5; y = Math.floor(y) - 0.5; - if (group.role === 'cell') { - const col = (group as any).col as number; - const row = (group as any).row as number; - if (table && col === table.colCount - 1) { - deltaWidth = 1; - } - if (table && row === table.rowCount - 1) { - deltaHeight = 1; - } - } + // if (group.role === 'cell') { + // const col = (group as any).col as number; + // const row = (group as any).row as number; + // if (table && col === table.colCount - 1) { + // deltaWidth = 1; + // } + // if (table && row === table.rowCount - 1) { + // deltaHeight = 1; + // } + // } } else { x = Math.floor(x) + 0.5; y = Math.floor(y) + 0.5; } + const { width: widthFroDraw, height: heightFroDraw } = getCellSizeForDraw( + group, + Math.ceil(width), + Math.ceil(height), + bottomRight + ); + if (cornerRadius) { // 测试后,cache对于重绘性能提升不大,但是在首屏有一定性能损耗,因此rect不再使用cache createRectPath(context, x, y, widthFroDraw + deltaWidth, heightFroDraw + deltaHeight, cornerRadius); @@ -998,7 +1018,7 @@ export class ClipBodyGroupAfterRenderContribution implements IGroupRenderContrib } } -function getCellSizeForDraw(group: any, width: number, height: number) { +function getCellSizeForDraw(group: any, width: number, height: number, bottomRight: boolean) { const table = group.stage.table as BaseTableAPI; if (group.role === 'cell') { let col = group.col as number; @@ -1009,24 +1029,25 @@ function getCellSizeForDraw(group: any, width: number, height: number) { row = mergeInfo.end.row; } - if (table && col === table.colCount - 1) { + if (table && col === table.colCount - 1 && !bottomRight) { width -= 1; - } else if (table && col === table.frozenColCount - 1 && table.scrollLeft) { + } else if (table && col === table.frozenColCount - 1 && table.scrollLeft && !bottomRight) { width -= 1; } - if (table && row === table.rowCount - 1) { + if (table && row === table.rowCount - 1 && !bottomRight) { height -= 1; - } else if (table && row === table.frozenRowCount - 1 && table.scrollTop) { + } else if (table && row === table.frozenRowCount - 1 && table.scrollTop && !bottomRight) { height -= 1; } } else if (group.role === 'corner-frozen') { - if (table && table.scrollLeft) { + if (table && table.scrollLeft && !bottomRight) { width -= 1; } - if (table && table.scrollTop) { + if (table && table.scrollTop && !bottomRight) { height -= 1; } } + return { width, height }; } diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts index 025fda7db..756ef2074 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts @@ -216,8 +216,10 @@ export function createCell( padding, textAlign, textBaseline, + mayHaveIcon, table, cellTheme, + range, isAsync ); } else if (type === 'video') { @@ -236,8 +238,10 @@ export function createCell( padding, textAlign, textBaseline, + mayHaveIcon, table, cellTheme, + range, isAsync ); } else if (type === 'chart') { @@ -340,9 +344,11 @@ export function createCell( padding, textAlign, textBaseline, + mayHaveIcon, table, cellTheme, define as CheckboxColumnDefine, + range, isAsync ); } else if (type === 'radio') { @@ -753,9 +759,14 @@ export function dealWithMergeCellSize( ) { 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); + // const cellGroup = table.scenegraph.getCell(col, row, true); + const cellGroup = table.scenegraph.highPerformanceGetCell(col, row, true); + + if (cellGroup.role !== 'cell') { + continue; + } - if (cellGroup.role === 'cell' && range.start.row !== range.end.row && cellGroup.contentHeight !== cellHeight) { + if (range.start.row !== range.end.row && cellGroup.contentHeight !== cellHeight) { updateCellContentHeight( cellGroup, cellHeight, @@ -768,7 +779,7 @@ export function dealWithMergeCellSize( // 'middle' ); } - if (cellGroup.role === 'cell' && range.start.col !== range.end.col && cellGroup.contentWidth !== cellWidth) { + if (range.start.col !== range.end.col && cellGroup.contentWidth !== cellWidth) { updateCellContentWidth( cellGroup, cellWidth, @@ -808,25 +819,33 @@ export function resizeCellGroup( cellGroup.forEachChildren((child: IGraphic) => { // 利用_dx hack解决掉 合并单元格的范围内的格子依次执行该方法 如果挨个调用updateCell的话 执行多次后dx累计问题 if (typeof child._dx === 'number') { + child.skipMergeUpdate = true; child.setAttributes({ dx: (child._dx ?? 0) + dx }); + child.skipMergeUpdate = false; } else { + child.skipMergeUpdate = true; child._dx = child.attribute.dx ?? 0; child.setAttributes({ dx: (child.attribute.dx ?? 0) + dx }); + child.skipMergeUpdate = false; } if (typeof child._dy === 'number') { + child.skipMergeUpdate = true; child.setAttributes({ dy: (child._dy ?? 0) + dy }); + child.skipMergeUpdate = false; } else { child._dy = child.attribute.dy ?? 0; + child.skipMergeUpdate = true; child.setAttributes({ dy: (child.attribute.dy ?? 0) + dy }); + child.skipMergeUpdate = false; } }); @@ -850,11 +869,13 @@ export function resizeCellGroup( const widthChange = rangeWidth !== cellGroup.attribute.width; const heightChange = rangeHeight !== cellGroup.attribute.height; + (cellGroup as any).skipMergeUpdate = true; cellGroup.setAttributes({ width: rangeWidth, height: rangeHeight, strokeArrayWidth: newLineWidth } as any); + (cellGroup as any).skipMergeUpdate = false; cellGroup.mergeStartCol = range.start.col; cellGroup.mergeStartRow = range.start.row; diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts index e6c86958c..e85099cc0 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/checkbox-cell.ts @@ -1,6 +1,6 @@ import type { IThemeSpec } from '@src/vrender'; import { Group } from '../../graphic/group'; -import type { CheckboxColumnDefine, CheckboxStyleOption } from '../../../ts-types'; +import type { CellInfo, CellRange, CheckboxColumnDefine, CheckboxStyleOption, SparklineSpec } from '../../../ts-types'; import type { BaseTableAPI } from '../../../ts-types/base-table'; import { isObject } from '@visactor/vutils'; import type { CheckboxAttributes } from '@visactor/vrender-components'; @@ -10,6 +10,7 @@ import { getOrApply } from '../../../tools/helper'; import type { CheckboxStyle } from '../../../body-helper/style/CheckboxStyle'; import { getProp } from '../../utils/get-prop'; import { getCellBorderStrokeWidth } from '../../utils/cell-border-stroke-width'; +import { dealWithIconLayout } from '../../utils/text-icon-layout'; export function createCheckboxCellGroup( cellGroup: Group | null, @@ -18,15 +19,17 @@ export function createCheckboxCellGroup( yOrigin: number, col: number, row: number, - colWidth: number | 'auto', + colWidth: number, width: number, height: number, padding: number[], textAlign: CanvasTextAlign, textBaseline: CanvasTextBaseline, + mayHaveIcon: boolean, table: BaseTableAPI, cellTheme: IThemeSpec, define: CheckboxColumnDefine, + range: CellRange | undefined, isAsync: boolean ) { // cell @@ -80,22 +83,84 @@ export function createCheckboxCellGroup( } } + let icons; + if (mayHaveIcon) { + let iconCol = col; + let iconRow = row; + if (range) { + iconCol = range.start.col; + iconRow = range.start.row; + } + icons = table.getCellIcons(iconCol, iconRow); + } + + let iconWidth = 0; + let cellLeftIconWidth = 0; + let cellRightIconWidth = 0; + if (Array.isArray(icons) && icons.length !== 0) { + const { leftIconWidth, rightIconWidth, absoluteLeftIconWidth, absoluteRightIconWidth } = dealWithIconLayout( + icons, + cellGroup, + range, + table + ); + + iconWidth = leftIconWidth + rightIconWidth; + cellLeftIconWidth = leftIconWidth; + cellRightIconWidth = rightIconWidth; + + // 更新各个部分横向位置 + cellGroup.forEachChildren((child: any) => { + if (child.role === 'icon-left') { + child.setAttribute('x', child.attribute.x + padding[3]); + } else if (child.role === 'icon-right') { + child.setAttribute('x', child.attribute.x + width - rightIconWidth - padding[1]); + } else if (child.role === 'icon-absolute-right') { + child.setAttribute('x', child.attribute.x + width - absoluteRightIconWidth - padding[1]); + } + }); + + // 更新各个部分纵向位置 + cellGroup.forEachChildren((child: any) => { + if (textBaseline === 'middle') { + child.setAttribute('y', (height - child.AABBBounds.height()) / 2); + } else if (textBaseline === 'bottom') { + child.setAttribute('y', height - child.AABBBounds.height() - padding[2]); + } else { + child.setAttribute('y', padding[0]); + } + }); + } + // checkbox - const checkboxComponent = createCheckbox(col, row, colWidth, width, height, padding, cellTheme, define, table); + const checkboxComponent = createCheckbox( + col, + row, + colWidth - iconWidth, + width, + height, + padding, + cellTheme, + define, + table + ); if (checkboxComponent) { cellGroup.appendChild(checkboxComponent); } checkboxComponent.render(); - width -= padding[1] + padding[3]; + width -= padding[1] + padding[3] + iconWidth; height -= padding[0] + padding[2]; if (textAlign === 'center') { - checkboxComponent.setAttribute('x', padding[3] + (width - checkboxComponent.AABBBounds.width()) / 2); + checkboxComponent.setAttribute( + 'x', + padding[3] + cellLeftIconWidth + (width - checkboxComponent.AABBBounds.width()) / 2 + ); } else if (textAlign === 'right') { - checkboxComponent.setAttribute('x', padding[3] + width - checkboxComponent.AABBBounds.width()); + checkboxComponent.setAttribute('x', padding[3] + cellLeftIconWidth + width - checkboxComponent.AABBBounds.width()); } else { - checkboxComponent.setAttribute('x', padding[3]); + checkboxComponent.setAttribute('x', padding[3] + cellLeftIconWidth); } if (textBaseline === 'middle') { diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts index 9541b2b88..6cb59600e 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts @@ -11,6 +11,8 @@ import { isValid } from '@visactor/vutils'; import { getQuadProps } from '../../utils/padding'; import { getCellBorderStrokeWidth } from '../../utils/cell-border-stroke-width'; import type { BaseTableAPI } from '../../../ts-types/base-table'; +import type { CellRange } from '../../../ts-types'; +import { dealWithIconLayout } from '../../utils/text-icon-layout'; export function createImageCellGroup( columnGroup: Group, @@ -25,8 +27,10 @@ export function createImageCellGroup( padding: [number, number, number, number], textAlign: CanvasTextAlign, textBaseline: CanvasTextBaseline, + mayHaveIcon: boolean, table: BaseTableAPI, cellTheme: IThemeSpec, + range: CellRange | undefined, isAsync: boolean ) { const headerStyle = table._getCellStyle(col, row); // to be fixed @@ -94,12 +98,64 @@ export function createImageCellGroup( columnGroup?.addCellGroup(cellGroup); } + let cellIcons; + if (mayHaveIcon) { + let iconCol = col; + let iconRow = row; + if (range) { + iconCol = range.start.col; + iconRow = range.start.row; + } + cellIcons = table.getCellIcons(iconCol, iconRow); + } + + let iconWidth = 0; + let cellLeftIconWidth = 0; + let cellRightIconWidth = 0; + if (Array.isArray(cellIcons) && cellIcons.length !== 0) { + const { leftIconWidth, rightIconWidth, absoluteLeftIconWidth, absoluteRightIconWidth } = dealWithIconLayout( + cellIcons, + cellGroup, + range, + table + ); + + iconWidth = leftIconWidth + rightIconWidth; + cellLeftIconWidth = leftIconWidth; + cellRightIconWidth = rightIconWidth; + + // 更新各个部分横向位置 + cellGroup.forEachChildren((child: any) => { + if (child.role === 'icon-left') { + child.setAttribute('x', child.attribute.x + padding[3]); + } else if (child.role === 'icon-right') { + child.setAttribute('x', child.attribute.x + width - rightIconWidth - padding[1]); + } else if (child.role === 'icon-absolute-right') { + child.setAttribute('x', child.attribute.x + width - absoluteRightIconWidth - padding[1]); + } + }); + + // 更新各个部分纵向位置 + cellGroup.forEachChildren((child: any) => { + if (textBaseline === 'middle') { + child.setAttribute('y', (height - child.AABBBounds.height()) / 2); + } else if (textBaseline === 'bottom') { + child.setAttribute('y', height - child.AABBBounds.height() - padding[2]); + } else { + child.setAttribute('y', padding[0]); + } + }); + + (cellGroup as any)._cellLeftIconWidth = cellLeftIconWidth; + (cellGroup as any)._cellRightIconWidth = cellRightIconWidth; + } + // image const value = table.getCellValue(col, row); const image: IImage = createImage({ x: padding[3], y: padding[0], - width: width - padding[1] - padding[3], + width: width - padding[1] - padding[3] - iconWidth, height: height - padding[0] - padding[2], image: value, //?? (regedIcons.damage_pic as any).svg, cursor: 'pointer' as Cursor @@ -147,10 +203,10 @@ export function createImageCellGroup( image.resources.has(image.attribute.image) && image.resources.get(image.attribute.image).state === 'success' ) { - updateImageCellContentWhileResize(cellGroup, col, row, table); + updateImageCellContentWhileResize(cellGroup, col, row, 0, 0, table); } else { image.successCallback = () => { - updateImageCellContentWhileResize(cellGroup, col, row, table); + updateImageCellContentWhileResize(cellGroup, col, row, 0, 0, table); }; } } @@ -233,7 +289,14 @@ export function _adjustWidthHeight( return false; } -export function updateImageCellContentWhileResize(cellGroup: Group, col: number, row: number, table: BaseTableAPI) { +export function updateImageCellContentWhileResize( + cellGroup: Group, + col: number, + row: number, + deltaX: number, + deltaY: number, + table: BaseTableAPI +) { const image = cellGroup.getChildByName('image') as Image; if (!image) { return; @@ -262,6 +325,9 @@ export function updateImageCellContentWhileResize(cellGroup: Group, col: number, const colEnd = cellGroup.mergeEndCol ?? cellGroup.col; const rowEnd = cellGroup.mergeEndCol ?? cellGroup.row; + const leftIconWidth = (cellGroup as any)._cellLeftIconWidth ?? 0; + const rightIconWidth = (cellGroup as any)._cellRightIconWidth ?? 0; + if ((image as any).keepAspectRatio) { const { width: imageWidth, height: imageHeight } = calcKeepAspectRatioSize( originImage.width || (originImage as any).videoWidth, @@ -304,11 +370,11 @@ export function updateImageCellContentWhileResize(cellGroup: Group, col: number, const cellGroup = table.scenegraph.getCell(col, row); const image = cellGroup.getChildByName('image') as Image; image?.setAttributes({ - x: padding[3], + x: leftIconWidth + padding[3], y: padding[0], // width: cellGroup.attribute.width - padding[1] - padding[3], // height: cellGroup.attribute.height - padding[0] - padding[2] - width: cellWidth - padding[1] - padding[3], + width: cellWidth - padding[1] - padding[3] - rightIconWidth - leftIconWidth, height: cellHeight - padding[0] - padding[2] }); } @@ -343,6 +409,30 @@ export function updateImageCellContentWhileResize(cellGroup: Group, col: number, } } + // 更新x方向位置 + cellGroup.forEachChildren((child: any) => { + if (child.role === 'icon-left') { + // do nothing + } else if (child.role === 'icon-right') { + child.setAttribute('x', child.attribute.x + deltaX); + } else if (child.role === 'icon-absolute-right') { + child.setAttribute('x', child.attribute.x + deltaX); + } + }); + + // 更新y方向位置 + cellGroup.forEachChildren((child: any) => { + if (child.type !== 'rect' && (!child.role || !child.role.startsWith('icon'))) { + // do nothing + } else if (textBaseline === 'middle') { + child.setAttribute('y', padding[0] + (cellHeight - padding[0] - padding[2] - child.AABBBounds.height()) / 2); + } else if (textBaseline === 'bottom') { + child.setAttribute('y', padding[0] + cellHeight - padding[0] - padding[2] - child.AABBBounds.height()); + } else { + child.setAttribute('y', padding[0]); + } + }); + if (isMerge) { updateImageDxDy( cellGroup.mergeStartCol, diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts index 20a0bd8fd..e2b024fa1 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts @@ -12,6 +12,8 @@ import { isValid } from '@visactor/vutils'; import type { BaseTableAPI } from '../../../ts-types/base-table'; import { getCellBorderStrokeWidth } from '../../utils/cell-border-stroke-width'; import { getQuadProps } from '../../utils/padding'; +import type { CellRange } from '../../../ts-types'; +import { dealWithIconLayout } from '../../utils/text-icon-layout'; const regedIcons = icons.get(); @@ -28,8 +30,10 @@ export function createVideoCellGroup( padding: [number, number, number, number], textAlign: CanvasTextAlign, textBaseline: CanvasTextBaseline, + mayHaveIcon: boolean, table: BaseTableAPI, cellTheme: IThemeSpec, + range: CellRange | undefined, isAsync: boolean ) { const headerStyle = table._getCellStyle(col, row); // to be fixed @@ -97,6 +101,58 @@ export function createVideoCellGroup( columnGroup?.addCellGroup(cellGroup); } + let cellIcons; + if (mayHaveIcon) { + let iconCol = col; + let iconRow = row; + if (range) { + iconCol = range.start.col; + iconRow = range.start.row; + } + cellIcons = table.getCellIcons(iconCol, iconRow); + } + + let iconWidth = 0; + let cellLeftIconWidth = 0; + let cellRightIconWidth = 0; + if (Array.isArray(cellIcons) && cellIcons.length !== 0) { + const { leftIconWidth, rightIconWidth, absoluteLeftIconWidth, absoluteRightIconWidth } = dealWithIconLayout( + cellIcons, + cellGroup, + range, + table + ); + + iconWidth = leftIconWidth + rightIconWidth; + cellLeftIconWidth = leftIconWidth; + cellRightIconWidth = rightIconWidth; + + // 更新各个部分横向位置 + cellGroup.forEachChildren((child: any) => { + if (child.role === 'icon-left') { + child.setAttribute('x', child.attribute.x + padding[3]); + } else if (child.role === 'icon-right') { + child.setAttribute('x', child.attribute.x + width - rightIconWidth - padding[1]); + } else if (child.role === 'icon-absolute-right') { + child.setAttribute('x', child.attribute.x + width - absoluteRightIconWidth - padding[1]); + } + }); + + // 更新各个部分纵向位置 + cellGroup.forEachChildren((child: any) => { + if (textBaseline === 'middle') { + child.setAttribute('y', (height - child.AABBBounds.height()) / 2); + } else if (textBaseline === 'bottom') { + child.setAttribute('y', height - child.AABBBounds.height() - padding[2]); + } else { + child.setAttribute('y', padding[0]); + } + }); + + (cellGroup as any)._cellLeftIconWidth = cellLeftIconWidth; + (cellGroup as any)._cellRightIconWidth = cellRightIconWidth; + } + // video const value = table.getCellValue(col, row); const video = document.createElement('video'); diff --git a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts index 801a24b9c..d52993b6a 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts @@ -107,6 +107,7 @@ export class SceneProxy { this.totalActualBodyColCount = totalActualBodyColCount; this.totalCol = this.bodyLeftCol + totalActualBodyColCount - 1; // 目标渐进完成的col this.colStart = this.bodyLeftCol; + this.colEnd = this.totalCol; // temp for first screen, will replace in createGroupForFirstScreen() const defaultColWidth = this.table.defaultColWidth; // const defaultColWidth = getDefaultHeight(this.table); this.taskColCount = Math.ceil(this.table.tableNoFrameWidth / defaultColWidth) * 1; @@ -140,6 +141,7 @@ export class SceneProxy { this.totalActualBodyRowCount = totalActualBodyRowCount; this.totalRow = this.bodyTopRow + totalActualBodyRowCount - 1; // 目标渐进完成的row this.rowStart = this.bodyTopRow; + this.rowEnd = this.totalRow; // temp for first screen, will replace in createGroupForFirstScreen() const defaultRowHeight = this.table.defaultRowHeight; // const defaultRowHeight = getDefaultWidth(this.table); this.taskRowCount = Math.ceil(this.table.tableNoFrameHeight / defaultRowHeight) * 1; @@ -275,6 +277,8 @@ export class SceneProxy { // compute rows height computeRowsHeight(this.table, this.currentRow + 1, endRow, false); + this.rowEnd = endRow; + if (this.table.frozenColCount) { // create row header row cellGroup let maxHeight = 0; @@ -343,9 +347,7 @@ export class SceneProxy { this.table.scenegraph.bodyGroup.setAttribute('height', maxHeight); this.currentRow = endRow; - this.rowEnd = endRow; this.rowUpdatePos = this.rowEnd; - // this.referenceRow = this.rowStart + Math.floor((endRow - this.rowStart) / 2); // update container group size and border this.table.scenegraph.updateContainer(); @@ -357,6 +359,8 @@ export class SceneProxy { const endCol = Math.min(this.totalCol, this.currentCol + onceCount); computeColsWidth(this.table, this.currentCol + 1, endCol); + this.colEnd = endCol; + // update last merge cell size for (let row = 0; row < this.table.rowCount; row++) { const cellGroup = this.highPerformanceGetCell(this.currentCol, row); @@ -439,13 +443,9 @@ export class SceneProxy { ); this.currentCol = endCol; - this.colEnd = endCol; this.colUpdatePos = this.colEnd; - // this.referenceCol = this.colStart + Math.floor((endCol - this.colStart) / 2); - // console.log('async', this.referenceCol, this.colStart, this.colEnd); // update container group size and border - // this.table.scenegraph.updateContainerAttrWidthAndX(); this.table.scenegraph.updateContainer(); this.table.scenegraph.updateBorderSizeAndPosition(); } diff --git a/packages/vtable/src/scenegraph/group-creater/progress/update-position/sort-horizontal.ts b/packages/vtable/src/scenegraph/group-creater/progress/update-position/sort-horizontal.ts index ce6cdb98d..f52e87eb2 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/update-position/sort-horizontal.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/update-position/sort-horizontal.ts @@ -14,6 +14,13 @@ export async function sortHorizontal(proxy: SceneProxy) { }); } }); + + // 更新同步范围 + const syncLeftCol = Math.max(proxy.bodyLeftCol, proxy.screenLeftCol - proxy.screenColCount * 1); + const syncRightCol = Math.min(proxy.bodyRightCol, proxy.screenLeftCol + proxy.screenColCount * 2); + + computeColsWidth(proxy.table, syncLeftCol, syncRightCol); + for (let col = proxy.colStart; col <= proxy.colEnd; col++) { // 将该列的chartInstance清除掉 const columnGroup = proxy.table.scenegraph.getColGroup(col); @@ -33,11 +40,6 @@ export async function sortHorizontal(proxy: SceneProxy) { proxy.table.scenegraph.updateCellContent(col, row); } } - // 更新同步范围 - const syncLeftCol = Math.max(proxy.bodyLeftCol, proxy.screenLeftCol - proxy.screenColCount * 1); - const syncRightCol = Math.min(proxy.bodyRightCol, proxy.screenLeftCol + proxy.screenColCount * 2); - - computeColsWidth(proxy.table, syncLeftCol, syncRightCol); updateColContent(syncLeftCol, syncRightCol, proxy); diff --git a/packages/vtable/src/scenegraph/group-creater/progress/update-position/sort-vertical.ts b/packages/vtable/src/scenegraph/group-creater/progress/update-position/sort-vertical.ts index 1dd54ec48..6f8a21d3d 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/update-position/sort-vertical.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/update-position/sort-vertical.ts @@ -15,6 +15,28 @@ export async function sortVertical(proxy: SceneProxy) { } }); + // 更新同步范围 + let syncTopRow; + let syncBottomRow; + if (proxy.table.heightMode === 'autoHeight') { + syncTopRow = proxy.rowStart; + syncBottomRow = proxy.rowEnd; + } else { + syncTopRow = Math.max(proxy.bodyTopRow, proxy.screenTopRow - proxy.screenRowCount * 1); + syncBottomRow = Math.min(proxy.bodyBottomRow, proxy.screenTopRow + proxy.screenRowCount * 2); + } + // console.log('sort更新同步范围', syncTopRow, syncBottomRow); + + const oldBodyHeight = proxy.table.getAllRowsHeight(); + + computeRowsHeight(proxy.table, syncTopRow, syncBottomRow); + + const newBodyHeight = proxy.table.getAllRowsHeight(); + + if (oldBodyHeight !== newBodyHeight) { + proxy.table.scenegraph.updateContainerHeight(proxy.table.frozenRowCount, newBodyHeight - oldBodyHeight); + } + for (let col = 0; col < proxy.table.frozenColCount ?? 0; col++) { // 将该列的chartInstance清除掉 const columnGroup = proxy.table.scenegraph.getColGroup(col); @@ -46,28 +68,6 @@ export async function sortVertical(proxy: SceneProxy) { } } - // 更新同步范围 - let syncTopRow; - let syncBottomRow; - if (proxy.table.heightMode === 'autoHeight') { - syncTopRow = proxy.rowStart; - syncBottomRow = proxy.rowEnd; - } else { - syncTopRow = Math.max(proxy.bodyTopRow, proxy.screenTopRow - proxy.screenRowCount * 1); - syncBottomRow = Math.min(proxy.bodyBottomRow, proxy.screenTopRow + proxy.screenRowCount * 2); - } - // console.log('sort更新同步范围', syncTopRow, syncBottomRow); - - const oldBodyHeight = proxy.table.getAllRowsHeight(); - - computeRowsHeight(proxy.table, syncTopRow, syncBottomRow); - - const newBodyHeight = proxy.table.getAllRowsHeight(); - - if (oldBodyHeight !== newBodyHeight) { - proxy.table.scenegraph.updateContainerHeight(proxy.table.frozenRowCount, newBodyHeight - oldBodyHeight); - } - updateRowContent(syncTopRow, syncBottomRow, proxy); if (proxy.table.heightMode === 'autoHeight') { diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts index fea59a25a..f7031bad5 100644 --- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts +++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts @@ -721,7 +721,7 @@ export function getAdaptiveWidth( if ( table.options.customConfig?.shrinkSparklineFirst && factor < 1 && - totalDrawWidth - actualWidth < totalSparklineAbleWidth + actualWidth - totalDrawWidth < totalSparklineAbleWidth ) { // only shrink sparkline column for (let i = 0; i < sparklineColumns.length; i++) { diff --git a/packages/vtable/src/scenegraph/layout/update-height.ts b/packages/vtable/src/scenegraph/layout/update-height.ts index bdc050d47..d973a150a 100644 --- a/packages/vtable/src/scenegraph/layout/update-height.ts +++ b/packages/vtable/src/scenegraph/layout/update-height.ts @@ -181,7 +181,7 @@ export function updateCellHeight( false ); } else if (type === 'image' || type === 'video') { - updateImageCellContentWhileResize(cell, col, row, scene.table); + updateImageCellContentWhileResize(cell, col, row, 0, detaY, scene.table); } else if (cell.firstChild?.name === 'axis') { (cell.firstChild as any)?.originAxis.resize(cell.attribute.width, cell.attribute.height); } else { diff --git a/packages/vtable/src/scenegraph/layout/update-width.ts b/packages/vtable/src/scenegraph/layout/update-width.ts index b5e246b09..81b480558 100644 --- a/packages/vtable/src/scenegraph/layout/update-width.ts +++ b/packages/vtable/src/scenegraph/layout/update-width.ts @@ -323,7 +323,7 @@ function updateCellWidth( // // 只更新背景边框 // const rect = cell.firstChild as Rect; // rect.setAttribute('width', cell.attribute.width); - updateImageCellContentWhileResize(cellGroup, col, row, scene.table); + updateImageCellContentWhileResize(cellGroup, col, row, detaX, 0, scene.table); } else if (cellGroup.firstChild?.name === 'axis') { // recreate axis component const axisConfig = scene.table.internalProps.layoutMap.getAxisConfigInPivotChart(col, row); diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index eb0cdeff7..6aae37c25 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -1544,7 +1544,7 @@ export class Scenegraph { const type = this.table.getBodyColumnType(col, row); const cellGroup = this.getCell(col, row); if (type === 'image' || type === 'video') { - updateImageCellContentWhileResize(cellGroup, col, row, this.table); + updateImageCellContentWhileResize(cellGroup, col, row, 0, 0, this.table); } } diff --git a/packages/vtable/src/scenegraph/utils/text-icon-layout.ts b/packages/vtable/src/scenegraph/utils/text-icon-layout.ts index 985d79e07..fd059fad4 100644 --- a/packages/vtable/src/scenegraph/utils/text-icon-layout.ts +++ b/packages/vtable/src/scenegraph/utils/text-icon-layout.ts @@ -54,22 +54,22 @@ export function createCellContent( cellTheme: IThemeSpec, range: CellRange | undefined ) { - const leftIcons: ColumnIconOption[] = []; - const rightIcons: ColumnIconOption[] = []; - const contentLeftIcons: ColumnIconOption[] = []; - const contentRightIcons: ColumnIconOption[] = []; - const inlineFrontIcons: ColumnIconOption[] = []; - const inlineEndIcons: ColumnIconOption[] = []; - const absoluteLeftIcons: ColumnIconOption[] = []; - const absoluteRightIcons: ColumnIconOption[] = []; + // const leftIcons: ColumnIconOption[] = []; + // const rightIcons: ColumnIconOption[] = []; + // const contentLeftIcons: ColumnIconOption[] = []; + // const contentRightIcons: ColumnIconOption[] = []; + // const inlineFrontIcons: ColumnIconOption[] = []; + // const inlineEndIcons: ColumnIconOption[] = []; + // const absoluteLeftIcons: ColumnIconOption[] = []; + // const absoluteRightIcons: ColumnIconOption[] = []; let contentWidth: number; let contentHeight: number; let leftIconWidth = 0; - let leftIconHeight = 0; + // let leftIconHeight = 0; let rightIconWidth = 0; - let rightIconHeight = 0; - let absoluteLeftIconWidth = 0; + // let rightIconHeight = 0; + // let absoluteLeftIconWidth = 0; let absoluteRightIconWidth = 0; if (!Array.isArray(icons) || icons.length === 0) { @@ -118,78 +118,98 @@ export function createCellContent( contentHeight = wrapText.AABBBounds.height(); } } else { - // icon分类 - icons.forEach(icon => { - switch (icon.positionType) { - case IconPosition.left: - leftIcons.push(icon); - break; - case IconPosition.right: - rightIcons.push(icon); - break; - case IconPosition.contentLeft: - contentLeftIcons.push(icon); - break; - case IconPosition.contentRight: - contentRightIcons.push(icon); - break; - // case IconPosition.absoluteLeft: - // absoluteLeftIcons.push(icon); - // break; - case IconPosition.absoluteRight: - absoluteRightIcons.push(icon); - break; - case IconPosition.inlineFront: - inlineFrontIcons.push(icon); - break; - case IconPosition.inlineEnd: - inlineEndIcons.push(icon); - break; - } - }); - - // 添加非cell icon & absolute icon - leftIcons.forEach(icon => { - const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); - iconMark.role = 'icon-left'; - iconMark.name = icon.name; - iconMark.setAttribute('x', leftIconWidth + (iconMark.attribute.marginLeft ?? 0)); - leftIconWidth += - iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); - leftIconHeight = Math.max(leftIconHeight, iconMark.AABBBounds.height()); - cellGroup.appendChild(iconMark); - }); - - rightIcons.forEach(icon => { - const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); - iconMark.role = 'icon-right'; - iconMark.name = icon.name; - iconMark.setAttribute('x', rightIconWidth + (iconMark.attribute.marginLeft ?? 0)); - rightIconWidth += - iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); - rightIconHeight = Math.max(rightIconHeight, iconMark.AABBBounds.height()); - cellGroup.appendChild(iconMark); - }); - - absoluteLeftIcons.forEach(icon => { - const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); - iconMark.role = 'icon-absolute-left'; - iconMark.name = icon.name; - iconMark.setAttribute('x', absoluteLeftIconWidth + (iconMark.attribute.marginLeft ?? 0)); - absoluteLeftIconWidth += - iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); - cellGroup.appendChild(iconMark); - }); - - absoluteRightIcons.forEach(icon => { - const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); - iconMark.role = 'icon-absolute-right'; - iconMark.name = icon.name; - iconMark.setAttribute('x', absoluteRightIconWidth + (iconMark.attribute.marginLeft ?? 0)); - absoluteRightIconWidth += - iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); - cellGroup.appendChild(iconMark); - }); + // // icon分类 + // icons.forEach(icon => { + // switch (icon.positionType) { + // case IconPosition.left: + // leftIcons.push(icon); + // break; + // case IconPosition.right: + // rightIcons.push(icon); + // break; + // case IconPosition.contentLeft: + // contentLeftIcons.push(icon); + // break; + // case IconPosition.contentRight: + // contentRightIcons.push(icon); + // break; + // // case IconPosition.absoluteLeft: + // // absoluteLeftIcons.push(icon); + // // break; + // case IconPosition.absoluteRight: + // absoluteRightIcons.push(icon); + // break; + // case IconPosition.inlineFront: + // inlineFrontIcons.push(icon); + // break; + // case IconPosition.inlineEnd: + // inlineEndIcons.push(icon); + // break; + // } + // }); + + // // 添加非cell icon & absolute icon + // leftIcons.forEach(icon => { + // const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); + // iconMark.role = 'icon-left'; + // iconMark.name = icon.name; + // iconMark.setAttribute('x', leftIconWidth + (iconMark.attribute.marginLeft ?? 0)); + // leftIconWidth += + // iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); + // leftIconHeight = Math.max(leftIconHeight, iconMark.AABBBounds.height()); + // cellGroup.appendChild(iconMark); + // }); + + // rightIcons.forEach(icon => { + // const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); + // iconMark.role = 'icon-right'; + // iconMark.name = icon.name; + // iconMark.setAttribute('x', rightIconWidth + (iconMark.attribute.marginLeft ?? 0)); + // rightIconWidth += + // iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); + // rightIconHeight = Math.max(rightIconHeight, iconMark.AABBBounds.height()); + // cellGroup.appendChild(iconMark); + // }); + + // absoluteLeftIcons.forEach(icon => { + // const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); + // iconMark.role = 'icon-absolute-left'; + // iconMark.name = icon.name; + // iconMark.setAttribute('x', absoluteLeftIconWidth + (iconMark.attribute.marginLeft ?? 0)); + // absoluteLeftIconWidth += + // iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); + // cellGroup.appendChild(iconMark); + // }); + + // absoluteRightIcons.forEach(icon => { + // const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); + // iconMark.role = 'icon-absolute-right'; + // iconMark.name = icon.name; + // iconMark.setAttribute('x', absoluteRightIconWidth + (iconMark.attribute.marginLeft ?? 0)); + // absoluteRightIconWidth += + // iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); + // cellGroup.appendChild(iconMark); + // }); + + const { + inlineFrontIcons, + inlineEndIcons, + contentLeftIcons, + contentRightIcons, + leftIconWidth: layoutLeftIconWidth, + // leftIconHeight: layoutLeftIconHeight, + rightIconWidth: layoutRightIconWidth, + // rightIconHeight: layoutRightIconHeight, + // absoluteLeftIconWidth: layoutAbsoluteLeftIconWidth, + absoluteRightIconWidth: layoutAbsoluteRightIconWidth + } = dealWithIconLayout(icons, cellGroup, range, table); + + leftIconWidth = layoutLeftIconWidth; + // leftIconHeight = layoutLeftIconHeight; + rightIconWidth = layoutRightIconWidth; + // rightIconHeight = layoutRightIconHeight; + // absoluteLeftIconWidth = layoutAbsoluteLeftIconWidth; + absoluteRightIconWidth = layoutAbsoluteRightIconWidth; // 添加text & content icon & inline icon let textMark; @@ -751,3 +771,116 @@ function isCellHeightUpdate(scene: Scenegraph, cellGroup: Group, newHeight: numb return false; } + +export function dealWithIconLayout( + icons: ColumnIconOption[], + cellGroup: Group, + range: CellRange | undefined, + table: BaseTableAPI +) { + const leftIcons: ColumnIconOption[] = []; + const rightIcons: ColumnIconOption[] = []; + const contentLeftIcons: ColumnIconOption[] = []; + const contentRightIcons: ColumnIconOption[] = []; + const inlineFrontIcons: ColumnIconOption[] = []; + const inlineEndIcons: ColumnIconOption[] = []; + const absoluteLeftIcons: ColumnIconOption[] = []; + const absoluteRightIcons: ColumnIconOption[] = []; + + let leftIconWidth = 0; + let leftIconHeight = 0; + let rightIconWidth = 0; + let rightIconHeight = 0; + let absoluteLeftIconWidth = 0; + let absoluteRightIconWidth = 0; + + // icon分类 + icons.forEach(icon => { + switch (icon.positionType) { + case IconPosition.left: + leftIcons.push(icon); + break; + case IconPosition.right: + rightIcons.push(icon); + break; + case IconPosition.contentLeft: + contentLeftIcons.push(icon); + break; + case IconPosition.contentRight: + contentRightIcons.push(icon); + break; + // case IconPosition.absoluteLeft: + // absoluteLeftIcons.push(icon); + // break; + case IconPosition.absoluteRight: + absoluteRightIcons.push(icon); + break; + case IconPosition.inlineFront: + inlineFrontIcons.push(icon); + break; + case IconPosition.inlineEnd: + inlineEndIcons.push(icon); + break; + } + }); + + // 添加非cell icon & absolute icon + leftIcons.forEach(icon => { + const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); + iconMark.role = 'icon-left'; + iconMark.name = icon.name; + iconMark.setAttribute('x', leftIconWidth + (iconMark.attribute.marginLeft ?? 0)); + leftIconWidth += + iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); + leftIconHeight = Math.max(leftIconHeight, iconMark.AABBBounds.height()); + cellGroup.appendChild(iconMark); + }); + + rightIcons.forEach(icon => { + const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); + iconMark.role = 'icon-right'; + iconMark.name = icon.name; + iconMark.setAttribute('x', rightIconWidth + (iconMark.attribute.marginLeft ?? 0)); + rightIconWidth += + iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); + rightIconHeight = Math.max(rightIconHeight, iconMark.AABBBounds.height()); + cellGroup.appendChild(iconMark); + }); + + absoluteLeftIcons.forEach(icon => { + const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); + iconMark.role = 'icon-absolute-left'; + iconMark.name = icon.name; + iconMark.setAttribute('x', absoluteLeftIconWidth + (iconMark.attribute.marginLeft ?? 0)); + absoluteLeftIconWidth += + iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); + cellGroup.appendChild(iconMark); + }); + + absoluteRightIcons.forEach(icon => { + const iconMark = dealWithIcon(icon, undefined, cellGroup.col, cellGroup.row, range, table); + iconMark.role = 'icon-absolute-right'; + iconMark.name = icon.name; + iconMark.setAttribute('x', absoluteRightIconWidth + (iconMark.attribute.marginLeft ?? 0)); + absoluteRightIconWidth += + iconMark.AABBBounds.width() + (iconMark.attribute.marginLeft ?? 0) + (iconMark.attribute.marginRight ?? 0); + cellGroup.appendChild(iconMark); + }); + + return { + leftIcons, + rightIcons, + contentLeftIcons, + contentRightIcons, + inlineFrontIcons, + inlineEndIcons, + absoluteLeftIcons, + absoluteRightIcons, + leftIconWidth, + leftIconHeight, + rightIconWidth, + rightIconHeight, + absoluteLeftIconWidth, + absoluteRightIconWidth + }; +} diff --git a/packages/vtable/src/state/cell-move/index.ts b/packages/vtable/src/state/cell-move/index.ts index a8dac8bae..8a23d0d28 100644 --- a/packages/vtable/src/state/cell-move/index.ts +++ b/packages/vtable/src/state/cell-move/index.ts @@ -29,8 +29,9 @@ export function startMoveCol(col: number, row: number, x: number, y: number, sta state.table.scenegraph.component.showMoveCol(col, row, delta); // 调整列顺序期间清空选中清空 + const isHasSelected = !!state.select.ranges?.length; state.table.stateManager.updateSelectPos(-1, -1); - + state.table.stateManager.endSelectCells(true, isHasSelected); state.table.scenegraph.updateNextFrame(); } diff --git a/packages/vtable/src/state/sort/index.ts b/packages/vtable/src/state/sort/index.ts index 16d3f6c28..c32383268 100644 --- a/packages/vtable/src/state/sort/index.ts +++ b/packages/vtable/src/state/sort/index.ts @@ -72,7 +72,9 @@ export function dealSort(col: number, row: number, table: ListTableAPI, event: E table.scenegraph.sortCell(); // 排序后,清除选中效果 + const isHasSelected = !!table.stateManager.select.ranges?.length; table.stateManager.updateSelectPos(-1, -1); + table.stateManager.endSelectCells(true, isHasSelected); } function executeSort(newState: SortState, table: BaseTableAPI, headerDefine: HeaderDefine): void { diff --git a/packages/vtable/src/state/state.ts b/packages/vtable/src/state/state.ts index 767261b2d..52fa80bd6 100644 --- a/packages/vtable/src/state/state.ts +++ b/packages/vtable/src/state/state.ts @@ -610,7 +610,7 @@ export class StateManager { isSelecting(): boolean { return this.select.selecting; } - endSelectCells(fireListener: boolean = true) { + endSelectCells(fireListener: boolean = true, fireClear: boolean = true) { if (this.select.selecting) { this.select.selecting = false; if (this.select.ranges.length === 0) { @@ -651,7 +651,7 @@ export class StateManager { col: lastCol, row: lastRow }); - } else if (fireListener) { + } else if (fireClear) { if (this.select.ranges.length === 0) { this.table.fireListeners(TABLE_EVENT_TYPE.SELECTED_CLEAR, {}); } @@ -676,8 +676,9 @@ export class StateManager { this.table.scenegraph.component.showResizeCol(col, y, isRightFrozen); // 调整列宽期间清空选中清空 + const isHasSelected = !!this.select.ranges?.length; this.updateSelectPos(-1, -1); - + this.endSelectCells(true, isHasSelected); this.table.scenegraph.updateNextFrame(); } updateResizeCol(xInTable: number, yInTable: number) { @@ -702,8 +703,9 @@ export class StateManager { this.table.scenegraph.component.showResizeRow(row, x, isBottomFrozen); // 调整列宽期间清空选中清空 + const isHasSelected = !!this.select.ranges?.length; this.updateSelectPos(-1, -1); - + this.endSelectCells(true, isHasSelected); this.table.scenegraph.updateNextFrame(); } updateResizeRow(xInTable: number, yInTable: number) { diff --git a/packages/vtable/src/tools/util.ts b/packages/vtable/src/tools/util.ts index 3e639a4d9..6a7f3f46e 100644 --- a/packages/vtable/src/tools/util.ts +++ b/packages/vtable/src/tools/util.ts @@ -422,3 +422,13 @@ export function deduplication(array: number[]) { } return result; } + +/** 判断div中的文本是否有被选中 */ +export function isDivSelected(div: HTMLDivElement) { + const selection = window.getSelection(); + if (selection.rangeCount) { + const range = selection.getRangeAt(0); + return range.endOffset > range.startOffset && div.contains(range.commonAncestorContainer); + } + return false; +} diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 767952da2..0e327ee71 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -577,6 +577,9 @@ export interface BaseTableAPI { columnWidthComputeMode?: 'normal' | 'only-header' | 'only-body'; + _rowRangeHeightsMap: Map; + _colRangeWidthsMap: Map; + /** 获取表格绘制的范围 不包括frame的宽度 */ getDrawRange: () => Rect; /** 将鼠标坐标值 转换成表格坐标系中的坐标位置 */ 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 b74ecf5cb..f2328514f 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 @@ -80,6 +80,8 @@ export interface IBasicColumnBodyDefine { // style?: ColumnStyleOption | null; /** 是否对相同内容合并单元格 **/ mergeCell?: MergeCellOption; + /** 是否隐藏 */ + hide?: boolean; customRender?: ICustomRender; customLayout?: ICustomLayout; editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index d5411dcf8..9dd669770 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -64,7 +64,7 @@ export type WidthModeDef = 'standard' | 'adaptive' | 'autoWidth'; export type HeightModeDef = 'standard' | 'adaptive' | 'autoHeight'; export type WidthAdaptiveModeDef = 'only-body' | 'all'; export type HeightAdaptiveModeDef = 'only-body' | 'all'; -export type ShowColumnRowType = 'column' | 'row' | 'none'; +export type ShowColumnRowType = 'column' | 'row' | 'none' | 'all'; /** 单元格所处表格哪部分 */ export type CellLocation = 'body' | 'rowHeader' | 'columnHeader' | 'cornerHeader'; export type CellSubLocation =