diff --git a/docs/assets/demo/en/export/table-export.md b/docs/assets/demo/en/export/table-export.md index beb110f7c..21d330930 100644 --- a/docs/assets/demo/en/export/table-export.md +++ b/docs/assets/demo/en/export/table-export.md @@ -4,8 +4,8 @@ group: export title: table export cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-table.png order: 4-6 -link: '../guide/components/dropdown' -option: ListTable#menu.contextMenuItems +link: '../../guide/export/excel' +# option: ListTable --- # table export diff --git a/docs/assets/demo/zh/export/table-export.md b/docs/assets/demo/zh/export/table-export.md index b2e6a03c1..83875ed52 100644 --- a/docs/assets/demo/zh/export/table-export.md +++ b/docs/assets/demo/zh/export/table-export.md @@ -4,8 +4,8 @@ group: export title: 表格导出 cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/pivot-table.png order: 4-6 -link: '../guide/components/dropdown' -option: ListTable#menu.contextMenuItems +link: '../../guide/export/excel' +# option: ListTable --- # 表格导出 diff --git a/docs/assets/guide/en/custom_define/custom_component.md b/docs/assets/guide/en/custom_define/custom_component.md new file mode 100644 index 000000000..3f9401d69 --- /dev/null +++ b/docs/assets/guide/en/custom_define/custom_component.md @@ -0,0 +1,174 @@ +# Custom interactive components + +Custom primitives in custom rendering and custom layout can use the components provided by `VRender`. Currently, the following components are supported: + +## TextAutoPoptip + +The `TextAutoPoptip` component is an interactive component provided by `VRender`. Its function is that when the text is too long and is omitted, hover over the text and a poptip will automatically pop up to display the entire content of the text. + +``` javascript livedemo template=vtable + const option = { + columns:[ + { + field: 'type', + title:'', + width:170, + headerStyle:{ + bgColor:'#4991e3' + }, + style:{ + fontFamily:'Arial', + fontWeight:600, + bgColor:'#4991e3', + fontSize:26, + padding:20, + lineHeight:32, + color:'white' + }, + }, + { + field: 'urgency', + title:'urgency', + width:400, + headerStyle:{ + lineHeight:50, + fontSize:26, + fontWeight:600, + bgColor:'#4991e3', + color:'white', + textAlign:'center' + }, + customLayout(args){ + const { width, height}= args.rect; + const {dataValue,table,row } =args; + const elements=[]; + let top=30; + const left=15; + + const container = new VTable.CustomLayout.Group({ + height, + width, + display: 'flex', + flexDirection: 'row', + alignContent: 'center', + alignItems: 'center', + justifyContent: 'space-around', + }); + + const text = new VTable.CustomLayout.Text({ + fill: '#000', + fontSize: 20, + fontWeight: 500, + textBaseline: 'top', + text: row===1? 'important but not urgency':'not important and not urgency', + + maxLineWidth: 200, + pickable: true + }); + + container.add(text); + + return { + rootContainer: container, + } + } + }, + { + field: 'not_urgency', + title:'not urgency', + width:400, + headerStyle:{ + lineHeight:50, + bgColor:'#4991e3', + color:'white', + textAlign:'center', + fontSize:26, + fontWeight:600, + }, + style:{ + fontFamily:'Arial', + fontSize:12, + fontWeight:'bold' + }, + customRender(args){ + console.log(args); + const { width, height}= args.rect; + const {dataValue,table,row} =args; + const elements=[]; + let top=30; + const left=15; + let maxWidth=0; + + elements.push({ + type: 'text', + fill: '#000', + fontSize: 20, + fontWeight: 500, + textBaseline: 'middle', + text: row===1? 'important but not urgency':'not important and not urgency', + x: left+50, + y: top-5, + + maxLineWidth: 200, + pickable: true + }); + + return { + elements, + expectedHeight:top+20, + expectedWidth: 100, + } + } + }, + ], + records:[ + { + 'type': 'important', + "urgency": ['crisis','urgent problem','tasks that must be completed within a limited time'], + "not_urgency": ['preventive measures','development relationship','identify new development opportunities','establish long-term goals'], + }, + { + 'type': 'Not\nimportant', + "urgency": ['Receive visitors','Certain calls, reports, letters, etc','Urgent matters','Public activities'], + "not_urgency": ['Trivial busy work','Some letters','Some phone calls','Time-killing activities','Some pleasant activities'], + }, + ], + defaultRowHeight:80, + heightMode:'autoHeight', + widthMode:'standard', + autoWrapText:true, + theme: VTable.themes.DEFAULT.extends({ + textPopTipStyle: { + // title: 'title' + } + }) + }; + +const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); +``` + +To use the `TextAutoPoptip` component, you need to configure the corresponding `text` primitive with `pickable: true` to enable interaction. At this time, the `TextAutoPoptip` component will automatically start when the `text` primitive is omitted by the `maxLineWidth` attribute. If you want to disable component retention interaction, you need to configure the `disableAutoClipedPoptip` attribute on the `text` primitive to `true`. + +The `poptip` style popped up by the `TextAutoPoptip` component can be configured in `theme.textPopTipStyle`. Some common properties are as follows: + +| Name | Type | Description | +| :-----| :---- | :---- | +|position|'auto' \| 'top' \| 'tl' \| 'tr' \| 'bottom' \| 'bl' \| 'br' \| 'left' \| 'lt' \| 'lb ' \| 'right' \| 'rt' \| 'rb'|`poptip` is displayed relative to the position of the primitive| +|title|string \| string[] \| number \| number[]|The content of `title` in `poptip`| +|titleStyle|Partial\|The style of the `title` content in `poptip`| +|titleFormatMethod|(t: string \| string[] \| number \| number[]) => string \| string[] \| number \| number[]|format method for `title` content in `poptip`| +|content|string \| string[] \| number \| number[]|The content of `content` in `poptip`, the default is the complete string| +|contentStyle|Partial|The style of the `content` content in `poptip`| +|contentFormatMethod|(t: string \| string[] \| number \| number[]) => string \| string[] \| number \| number[]|The format method of `content` in `poptip`| +|space|number|Distance between `title` and `content`| +|padding|Padding|padding in `poptip`| +|panel|BackgroundAttributes & ISymbolGraphicAttribute & {space?:number;}|Background style in `poptip`| +|minWidth|number|Maximum width in `poptip`| +|maxWidth|number|The minimum width in `poptip`| +|maxWidthPercent|number|Maximum width percentage in `poptip`| +|visible|boolean|whether `poptip` is visible| +|visibleFunc|(graphic: IGraphic) => boolean|whether `poptip` is visible function| +|dx|number|`poptip`x-direction offset| +|dy|number|`poptip`y direction offset| + + \ No newline at end of file diff --git a/docs/assets/guide/menu.json b/docs/assets/guide/menu.json index ee4b89987..8096fcdbf 100644 --- a/docs/assets/guide/menu.json +++ b/docs/assets/guide/menu.json @@ -354,6 +354,13 @@ "zh": "单元格自定义渲染", "en": "custom_render" } + }, + { + "path": "custom_component", + "title": { + "zh": "自定义交互组件", + "en": "custom_interactive_component" + } } ] }, diff --git a/docs/assets/guide/zh/custom_define/custom_component.md b/docs/assets/guide/zh/custom_define/custom_component.md new file mode 100644 index 000000000..7f59a69a8 --- /dev/null +++ b/docs/assets/guide/zh/custom_define/custom_component.md @@ -0,0 +1,174 @@ +# 自定义交互组件 + +自定义渲染和自定义布局中的自定义图元,可以使用`VRender`提供的组件,目前支持的有以下组件: + +## TextAutoPoptip + +`TextAutoPoptip`组件是`VRender`提供的一个交互组件,它的功能是在文本过长被省略时,hover到文本上,会自动弹出一个poptip,展示文本的全部内容。 + +``` javascript livedemo template=vtable + const option = { + columns:[ + { + field: 'type', + title:'', + width:170, + headerStyle:{ + bgColor:'#4991e3' + }, + style:{ + fontFamily:'Arial', + fontWeight:600, + bgColor:'#4991e3', + fontSize:26, + padding:20, + lineHeight:32, + color:'white' + }, + }, + { + field: 'urgency', + title:'urgency', + width:400, + headerStyle:{ + lineHeight:50, + fontSize:26, + fontWeight:600, + bgColor:'#4991e3', + color:'white', + textAlign:'center' + }, + customLayout(args){ + const { width, height}= args.rect; + const {dataValue,table,row } =args; + const elements=[]; + let top=30; + const left=15; + + const container = new VTable.CustomLayout.Group({ + height, + width, + display: 'flex', + flexDirection: 'row', + alignContent: 'center', + alignItems: 'center', + justifyContent: 'space-around', + }); + + const text = new VTable.CustomLayout.Text({ + fill: '#000', + fontSize: 20, + fontWeight: 500, + textBaseline: 'top', + text: row===1? 'important but not urgency':'not important and not urgency', + + maxLineWidth: 200, + pickable: true + }); + + container.add(text); + + return { + rootContainer: container, + } + } + }, + { + field: 'not_urgency', + title:'not urgency', + width:400, + headerStyle:{ + lineHeight:50, + bgColor:'#4991e3', + color:'white', + textAlign:'center', + fontSize:26, + fontWeight:600, + }, + style:{ + fontFamily:'Arial', + fontSize:12, + fontWeight:'bold' + }, + customRender(args){ + console.log(args); + const { width, height}= args.rect; + const {dataValue,table,row} =args; + const elements=[]; + let top=30; + const left=15; + let maxWidth=0; + + elements.push({ + type: 'text', + fill: '#000', + fontSize: 20, + fontWeight: 500, + textBaseline: 'middle', + text: row===1? 'important but not urgency':'not important and not urgency', + x: left+50, + y: top-5, + + maxLineWidth: 200, + pickable: true + }); + + return { + elements, + expectedHeight:top+20, + expectedWidth: 100, + } + } + }, + ], + records:[ + { + 'type': 'important', + "urgency": ['crisis','urgent problem','tasks that must be completed within a limited time'], + "not_urgency": ['preventive measures','development relationship','identify new development opportunities','establish long-term goals'], + }, + { + 'type': 'Not\nimportant', + "urgency": ['Receive visitors','Certain calls, reports, letters, etc','Urgent matters','Public activities'], + "not_urgency": ['Trivial busy work','Some letters','Some phone calls','Time-killing activities','Some pleasant activities'], + }, + ], + defaultRowHeight:80, + heightMode:'autoHeight', + widthMode:'standard', + autoWrapText:true, + theme: VTable.themes.DEFAULT.extends({ + textPopTipStyle: { + // title: 'title' + } + }) + }; + +const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); +``` + +使用`TextAutoPoptip`组件需要把相应的`text`图元配置`pickable: true`,开启交互,此时`TextAutoPoptip`组件在`text`图元被`maxLineWidth`属性省略时自动启动。如果想禁用组件保留交互,需要在`text`图元上配置`disableAutoClipedPoptip`属性为`true`。 + +`TextAutoPoptip`组件弹出的`poptip`样式,可以在`theme.textPopTipStyle`中配置,部分常用属性如下: + +| Name | Type | Description | +| :-----| :---- | :---- | +|position|'auto' \| 'top' \| 'tl' \| 'tr' \| 'bottom' \| 'bl' \| 'br' \| 'left' \| 'lt' \| 'lb' \| 'right' \| 'rt' \| 'rb'|`poptip`显示在相对于图元的位置| +|title|string \| string[] \| number \| number[]|`poptip`中`title`内容| +|titleStyle|Partial\|`poptip`中`title`内容的样式| +|titleFormatMethod|(t: string \| string[] \| number \| number[]) => string \| string[] \| number \| number[]|`poptip`中`title`内容的format方法| +|content|string \| string[] \| number \| number[]|`poptip`中`content`内容,默认是完整字符串| +|contentStyle|Partial|`poptip`中`content`内容的样式| +|contentFormatMethod|(t: string \| string[] \| number \| number[]) => string \| string[] \| number \| number[]|`poptip`中`content`内容的format方法| +|space|number|`title`和`content`距离| +|padding|Padding|`poptip`中的padding| +|panel|BackgroundAttributes & ISymbolGraphicAttribute & {space?:number;}|`poptip`中的背景样式| +|minWidth|number|`poptip`中的最大宽度| +|maxWidth|number|`poptip`中的最小宽度| +|maxWidthPercent|number|`poptip`中的最大宽度百分比| +|visible|boolean|`poptip`是否可见| +|visibleFunc|(graphic: IGraphic) => boolean|`poptip`是否可见函数| +|dx|number|`poptip`x方向偏移| +|dy|number|`poptip`y方向偏移| + + \ No newline at end of file diff --git a/docs/assets/option/en/custom-element/base-custom-element.md b/docs/assets/option/en/custom-element/base-custom-element.md index de8ab1b55..0e290b140 100644 --- a/docs/assets/option/en/custom-element/base-custom-element.md +++ b/docs/assets/option/en/custom-element/base-custom-element.md @@ -24,9 +24,9 @@ ${prefix} dy (number) The y-offset of the element. -${prefix} clickable (boolean) +${prefix} pickable (boolean) -Whether the element is clickable. +Whether the element is interactive, the interactive element will be displayed in the `target` of the interaction event callback parameter when it is interacted. ${prefix} cursor (string) diff --git a/docs/assets/option/zh/custom-element/base-custom-element.md b/docs/assets/option/zh/custom-element/base-custom-element.md index 4ea57093f..d928a0465 100644 --- a/docs/assets/option/zh/custom-element/base-custom-element.md +++ b/docs/assets/option/zh/custom-element/base-custom-element.md @@ -24,9 +24,9 @@ ${prefix} dy (number) 元素的 y 偏移量。 -${prefix} clickable (boolean) +${prefix} pickable (boolean) -元素是否可点击。 +元素是否可交互,可交互的图元会在被交互时会显示在交互事件回调参数的`target`中。 ${prefix} cursor (string) diff --git a/docs/vite.config.js b/docs/vite.config.js index b6cb88f1f..0d7619437 100644 --- a/docs/vite.config.js +++ b/docs/vite.config.js @@ -17,7 +17,8 @@ export default { resolve: { alias: { '@visactor/vtable': path.resolve('../packages/vtable/src/index.ts'), - '@visactor/vtable-editors': path.resolve('../packages/vtable-editors/src/index.ts') + '@visactor/vtable-editors': path.resolve('../packages/vtable-editors/src/index.ts'), + '@visactor/vtable-export': path.resolve('../packages/vtable-export/src/index.ts') } }, plugins: [react()] diff --git a/packages/vtable/examples/custom/custom-render.ts b/packages/vtable/examples/custom/custom-render.ts new file mode 100644 index 000000000..384071e70 --- /dev/null +++ b/packages/vtable/examples/custom/custom-render.ts @@ -0,0 +1,217 @@ +/* eslint-disable */ +import * as VTable from '../../src'; +import VChart from '@visactor/vchart'; +import { bindDebugTool } from '../../src/scenegraph/debug-tool'; + +const CONTAINER_ID = 'vTable'; +VTable.register.chartModule('vchart', VChart); +const padding = [10, 20, 10, 20]; + +export function createTable() { + const option = { + columns: [ + { + field: 'type', + title: '', + width: 170, + headerStyle: { + bgColor: '#4991e3' + }, + style: { + fontFamily: 'Arial', + fontWeight: 600, + bgColor: '#4991e3', + fontSize: 26, + padding: 20, + lineHeight: 32, + color: 'white' + } + }, + { + field: 'urgency', + title: 'urgency', + width: 400, + headerStyle: { + lineHeight: 50, + fontSize: 26, + fontWeight: 600, + bgColor: '#4991e3', + color: 'white', + textAlign: 'center' + }, + customLayout(args) { + const { width, height } = args.rect; + const { dataValue, table, row } = args; + const elements = []; + let top = 30; + const left = 15; + + const container = new VTable.CustomLayout.Group({ + height, + width, + display: 'flex', + flexDirection: 'row', + alignContent: 'center', + alignItems: 'center', + justifyContent: 'space-around' + }); + + const text = new VTable.CustomLayout.Text({ + fill: '#000', + fontSize: 20, + fontWeight: 500, + textBaseline: 'top', + text: row === 1 ? 'important but not urgency' : 'not important and not urgency', + + maxLineWidth: 200, + pickable: true + }); + + container.add(text); + + return { + rootContainer: container + }; + } + }, + { + field: 'not_urgency', + title: 'not urgency', + width: 400, + headerStyle: { + lineHeight: 50, + bgColor: '#4991e3', + color: 'white', + textAlign: 'center', + fontSize: 26, + fontWeight: 600 + }, + style: { + fontFamily: 'Arial', + fontSize: 12, + fontWeight: 'bold' + }, + customRender(args) { + console.log(args); + const { width, height } = args.rect; + const { dataValue, table, row } = args; + const elements = []; + let top = 30; + const left = 15; + let maxWidth = 0; + + elements.push({ + type: 'text', + fill: '#000', + fontSize: 20, + fontWeight: 500, + textBaseline: 'middle', + text: row === 1 ? 'important but not urgency' : 'not important and not urgency', + x: left + 50, + y: top - 5, + + maxLineWidth: 200, + pickable: true + }); + + return { + elements, + expectedHeight: top + 20, + expectedWidth: 100 + }; + } + } + ], + records: [ + { + type: 'important', + urgency: ['crisis', 'urgent problem', 'tasks that must be completed within a limited time'], + not_urgency: [ + 'preventive measures', + 'development relationship', + 'identify new development opportunities', + 'establish long-term goals' + ] + }, + { + type: 'Not\nimportant', + urgency: ['Receive visitors', 'Certain calls, reports, letters, etc', 'Urgent matters', 'Public activities'], + not_urgency: [ + 'Trivial busy work', + 'Some letters', + 'Some phone calls', + 'Time-killing activities', + 'Some pleasant activities' + ] + } + ], + defaultRowHeight: 80, + heightMode: 'autoHeight', + widthMode: 'standard', + autoWrapText: true, + theme: VTable.themes.DEFAULT.extends({ + textPopTipStyle: { + title: 'title' + } + }) + }; + + const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); + window['tableInstance'] = tableInstance; + + bindDebugTool(tableInstance.scenegraph.stage, { + customGrapicKeys: ['col', 'row'] + }); +} + +const titles = [, '近1月指标值', '近1月目标值', '近1月环比', '近1月目标完成度', '近1月年同比', 'title']; +const dates = [, '04.01 - 04-15', '04', '03.01 - 03.15', '04', '2020.04.01 - 2020.04.15', 'date']; + +function customHeader(args: any) { + const { table, row, col, rect } = args; + const { height, width } = rect ?? table.getCellRect(col, row); + const record = table.getRecordByCell(col, row); + + const hasUnderline = true; + const container = new VTable.CustomLayout.Group({ + height, + width, + display: 'flex', + flexDirection: 'column', + flexWrap: 'nowrap', + justifyContent: 'space-around', + // alignItems: 'flex-end', + // alignItems: 'center', + alignItems: 'flex-start' + }); + + const title = new VTable.CustomLayout.Text({ + text: titles[col], + fontSize: 12, + lineHeight: 18, + fontFamily: 'Arial', + fontWeight: 'bold', + fill: '#000', + underline: hasUnderline ? 1 : undefined + // boundsPadding: [0, padding[1], 0, padding[3]], + }); + + const date = new VTable.CustomLayout.Text({ + text: dates[col], + fontSize: 12, + lineHeight: 18, + fontFamily: 'Arial', + fontWeight: 'bold', + fill: '#000' + // boundsPadding: [0, padding[1], 0, padding[3]], + }); + + container.add(title); + container.add(date); + + return { + rootContainer: container, + renderDefault: false, + enableCellPadding: true + }; +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index f59cd5163..8a5767ae1 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -557,6 +557,10 @@ export const menus = [ { path: 'custom', name: 'custom-header' + }, + { + path: 'custom', + name: 'custom-render' } ] }, diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index b73958950..52b7bf905 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -1171,4 +1171,24 @@ export class ListTable extends BaseTable implements ListTableAPI { // this.fireListeners(TABLE_EVENT_TYPE.ADD_RECORD, { row }); } } + + hasCustomRenderOrLayout() { + const { headerObjects } = this.internalProps.layoutMap; + if (this.options.customRender) { + return true; + } + + for (let i = 0; i < headerObjects.length; i++) { + const headerObject = headerObjects[i]; + if ( + headerObject?.define?.customLayout || + headerObject?.define?.headerCustomLayout || + headerObject?.define?.customRender || + headerObject?.define?.headerCustomRender + ) { + return true; + } + } + return false; + } } diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index 7d6b4d363..6008e74af 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -1177,4 +1177,36 @@ export class PivotChart extends BaseTable implements PivotChartAPI { } this.eventManager.updateEventBinder(); } + + hasCustomRenderOrLayout() { + if (this.options.customRender) { + return true; + } + const { columnsDefine, rowsDefine, indicatorsDefine } = this.internalProps.layoutMap; + for (let i = 0; i < columnsDefine.length; i++) { + const columnDefine = columnsDefine[i]; + if (typeof columnDefine !== 'string' && (columnDefine.headerCustomLayout || columnDefine.headerCustomRender)) { + return true; + } + } + for (let i = 0; i < rowsDefine.length; i++) { + const rowDefine = rowsDefine[i]; + if (typeof rowDefine !== 'string' && (rowDefine.headerCustomLayout || rowDefine.headerCustomRender)) { + return true; + } + } + for (let i = 0; i < indicatorsDefine.length; i++) { + const indicatorDefine = indicatorsDefine[i]; + if ( + typeof indicatorDefine !== 'string' && + (indicatorDefine.customLayout || + indicatorDefine.headerCustomLayout || + indicatorDefine.customRender || + indicatorDefine.headerCustomRender) + ) { + return true; + } + } + return false; + } } diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 3f8a46e15..1f5c20bc8 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -1049,4 +1049,36 @@ export class PivotTable extends BaseTable implements PivotTableAPI { }); this.scenegraph.updateNextFrame(); } + + hasCustomRenderOrLayout() { + if (this.options.customRender) { + return true; + } + const { columnsDefine, rowsDefine, indicatorsDefine } = this.internalProps.layoutMap; + for (let i = 0; i < columnsDefine.length; i++) { + const columnDefine = columnsDefine[i]; + if (typeof columnDefine !== 'string' && (columnDefine.headerCustomLayout || columnDefine.headerCustomRender)) { + return true; + } + } + for (let i = 0; i < rowsDefine.length; i++) { + const rowDefine = rowsDefine[i]; + if (typeof rowDefine !== 'string' && (rowDefine.headerCustomLayout || rowDefine.headerCustomRender)) { + return true; + } + } + for (let i = 0; i < indicatorsDefine.length; i++) { + const indicatorDefine = indicatorsDefine[i]; + if ( + typeof indicatorDefine !== 'string' && + (indicatorDefine.customLayout || + indicatorDefine.headerCustomLayout || + indicatorDefine.customRender || + indicatorDefine.headerCustomRender) + ) { + return true; + } + } + return false; + } } diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 5dd0ddd64..676dca9db 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -242,6 +242,10 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { } internalProps.handler = new EventHandler(); + if (isNumber(this.options.resizeTime)) { + internalProps.handler.resizeTime = this.options.resizeTime; + } + internalProps.pixelRatio = pixelRatio; internalProps.frozenColCount = frozenColCount; @@ -2381,6 +2385,9 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @param pagination 要修改页码的信息 */ abstract updatePagination(pagination: IPagination): void; + + abstract hasCustomRenderOrLayout(): boolean; + get allowFrozenColCount(): number { return this.internalProps.allowFrozenColCount; } diff --git a/packages/vtable/src/event/EventHandler.ts b/packages/vtable/src/event/EventHandler.ts index 96c116f28..c7fd2ce62 100644 --- a/packages/vtable/src/event/EventHandler.ts +++ b/packages/vtable/src/event/EventHandler.ts @@ -108,6 +108,8 @@ export class ResizeObserver { } export class EventHandler { + resizeTime?: number; + private listeners: { [key: string]: EventListenerObject; } = {}; @@ -130,7 +132,7 @@ export class EventHandler { if (type !== 'resize' || (target as Window) === window) { (target as EventTarget)?.addEventListener(type, listener, ...(options as [])); } else { - const resizeObserver = new ResizeObserver(target as HTMLElement, listener); + const resizeObserver = new ResizeObserver(target as HTMLElement, listener, this.resizeTime); this.reseizeListeners[id] = resizeObserver; } } diff --git a/packages/vtable/src/scenegraph/component/custom.ts b/packages/vtable/src/scenegraph/component/custom.ts index c6d4f4bd4..2763b8f91 100644 --- a/packages/vtable/src/scenegraph/component/custom.ts +++ b/packages/vtable/src/scenegraph/component/custom.ts @@ -138,6 +138,9 @@ function adjustElementToGroup( const elementsAdjusted = adjustElementsPos(elements, width, height, value); elementsAdjusted.forEach(element => { + if ((element as any).clickable) { + element.pickable = (element as any).clickable; + } switch (element.type) { case 'arc': const arc = createArc({ @@ -150,7 +153,7 @@ function adjustElementToGroup( outerRadius: element.radius as number, startAngle: element.startAngle as number, endAngle: element.endAngle as number, - pickable: !!element.clickable, + pickable: !!element.pickable, cursor: element.cursor as Cursor }); customGroup.appendChild(arc); @@ -175,7 +178,7 @@ function adjustElementToGroup( const text = new Text( Object.assign( { - pickable: !!element.clickable, + pickable: !!element.pickable, fill: element.color ?? element.fill }, element as any @@ -194,7 +197,7 @@ function adjustElementToGroup( cornerRadius: element.radius as number, fill: element.fill as string, stroke: element.stroke as string, - pickable: !!element.clickable, + pickable: !!element.pickable, cursor: element.cursor as Cursor }); customGroup.appendChild(rect); @@ -208,7 +211,7 @@ function adjustElementToGroup( radius: element.radius as number, fill: element.fill as string, stroke: element.stroke as string, - pickable: !!element.clickable, + pickable: !!element.pickable, cursor: element.cursor as Cursor }); customGroup.appendChild(circle); @@ -225,7 +228,7 @@ function adjustElementToGroup( backgroundWidth: element.hover ? ((element.hover.width ?? element.width) as number) : undefined, backgroundHeight: element.hover ? ((element.hover.width ?? element.width) as number) : undefined, backgroundColor: element.hover ? element.hover.bgColor ?? 'rgba(22,44,66,0.2)' : undefined, - pickable: !!element.clickable, + pickable: !!element.pickable, cursor: element.cursor as Cursor }); icon.role = 'icon-custom'; @@ -243,7 +246,7 @@ function adjustElementToGroup( backgroundWidth: element.hover ? ((element.hover.width ?? element.width) as number) : undefined, backgroundHeight: element.hover ? ((element.hover.width ?? element.width) as number) : undefined, backgroundColor: element.hover ? element.hover.bgColor ?? 'rgba(22,44,66,0.2)' : undefined, - pickable: !!element.clickable, + pickable: !!element.pickable, cursor: element.cursor as Cursor, shape: element.shape }); @@ -255,7 +258,7 @@ function adjustElementToGroup( const line = createLine({ points: element.points, stroke: element.stroke as string, - pickable: !!element.clickable, + pickable: !!element.pickable, cursor: element.cursor as Cursor }); customGroup.appendChild(line); diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index f56097262..820013a3d 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -1,4 +1,4 @@ -import type { IStage, IRect, ITextCache, INode, Text } from '@visactor/vrender'; +import type { IStage, IRect, ITextCache, INode, Text, RichText } from '@visactor/vrender'; import { createStage, createRect, IContainPointMode, container, vglobal } from '@visactor/vrender'; import type { CellRange, CellSubLocation } from '../ts-types'; import { @@ -70,35 +70,6 @@ container.load(textMeasureModule); // container.load(contextModule); // console.log(container); -const poptipStyle = { - visible: true, - position: 'auto', - padding: 8, - titleStyle: { - fontSize: 12, - fontWeight: 'bold', - fill: '#4E5969' - }, - contentStyle: { - fontSize: 12, - fill: '#4E5969' - }, - panel: { - visible: true, - fill: '#fff', - stroke: '#ffffff', - lineWidth: 0, - cornerRadius: 3, - shadowBlur: 12, - shadowOffsetX: 0, - shadowOffsetY: 4, - shadowColor: 'rgba(0, 0, 0, 0.1)', - size: 0, - space: 12 - } - // maxWidthPercent: 0.8 -}; - export type MergeMap = Map< string, { @@ -147,7 +118,7 @@ export class Scenegraph { this.clear = true; this.mergeMap = new Map(); - setPoptipTheme(poptipStyle as any); + setPoptipTheme(this.table.theme.textPopTipStyle); let width; let height; if (Env.mode === 'node') { @@ -167,7 +138,7 @@ export class Scenegraph { background: table.theme.underlayBackgroundColor, dpr: table.internalProps.pixelRatio, enableLayout: true, - pluginList: table.isPivotChart() ? ['poptipForText'] : undefined, + // pluginList: table.isPivotChart() ? ['poptipForText'] : undefined, afterRender: () => { this.table.fireListeners('after_render', null); // console.trace('after_render'); @@ -242,6 +213,14 @@ export class Scenegraph { * @return {*} */ clearCells() { + // unbind AutoPoptip + if (this.table.isPivotChart() || this.table.hasCustomRenderOrLayout()) { + // bind for axis label in pivotChart + this.stage.pluginService.findPluginsByName('poptipForText').forEach(plugin => { + plugin.deactivate(this.stage.pluginService); + }); + } + this.clear = true; this.hasFrozen = false; this.mergeMap.clear(); @@ -350,6 +329,16 @@ export class Scenegraph { * @return {*} */ createSceneGraph() { + // bind AutoPoptip + if (this.table.isPivotChart() || this.table.hasCustomRenderOrLayout()) { + // bind for axis label in pivotChart + (this.stage.pluginService as any).autoEnablePlugins.getContributions().forEach((p: any) => { + if (p.name === 'poptipForText') { + this.stage.pluginService.register(p); + } + }); + } + this.clear = false; // this.frozenColCount = this.table.rowHeaderLevelCount; this.frozenColCount = this.table.frozenColCount; @@ -1538,10 +1527,8 @@ export class Scenegraph { getCellOverflowText(col: number, row: number): string | null { const cellGroup = this.getCell(col, row); - const text = cellGroup.getChildByName('text', true) as unknown as Text; - // if (text && text.cache?.clipedText !== text.attribute.text) { - // return text.attribute.text as string; - // } + const text = cellGroup.getChildByName('text', true) as unknown as Text | RichText; + if (text && text.type === 'text') { const textAttributeStr = isArray(text.attribute.text) ? text.attribute.text.join('') @@ -1557,6 +1544,16 @@ export class Scenegraph { if (cacheStr !== textAttributeStr) { return textAttributeStr; } + } else if (text && text.type === 'richtext') { + const richtext = text; + if ( + richtext.attribute.ellipsis && + richtext._frameCache && + richtext.attribute.height < richtext._frameCache.actualHeight + ) { + const textConfig = richtext.attribute.textConfig.find((item: any) => item.text); + return (textConfig as any).text as string; + } } return null; } diff --git a/packages/vtable/src/scenegraph/utils/text-icon-layout.ts b/packages/vtable/src/scenegraph/utils/text-icon-layout.ts index 5a1f9a743..414ce62a4 100644 --- a/packages/vtable/src/scenegraph/utils/text-icon-layout.ts +++ b/packages/vtable/src/scenegraph/utils/text-icon-layout.ts @@ -217,7 +217,9 @@ export function createCellContent( width: autoColWidth ? 0 : cellWidth - (padding[1] + padding[3]) - leftIconWidth - rightIconWidth, height: autoRowHeight ? 0 : cellHeight - (padding[0] + padding[2]), textConfig, - verticalDirection: autoRowHeight ? 'top' : (textBaseline as any) + verticalDirection: autoRowHeight ? 'top' : (textBaseline as any), + + ellipsis: textOption.ellipsis // verticalDirection: textBaseline as any // textAlign: textAlign as any, // textBaseline: textBaseline as any, diff --git a/packages/vtable/src/themes/component.ts b/packages/vtable/src/themes/component.ts index 153e5bafc..3bba79f9a 100644 --- a/packages/vtable/src/themes/component.ts +++ b/packages/vtable/src/themes/component.ts @@ -19,3 +19,32 @@ function getSingleAxisStyle(axisStyle?: RequiredTableThemeDefine['axisStyle']['d return axisStyle; // to do: turn into get mode } + +export const defalutPoptipStyle = { + visible: true, + position: 'auto', + padding: 8, + titleStyle: { + fontSize: 12, + fontWeight: 'bold', + fill: '#4E5969' + }, + contentStyle: { + fontSize: 12, + fill: '#4E5969' + }, + panel: { + visible: true, + fill: '#fff', + stroke: '#ffffff', + lineWidth: 0, + cornerRadius: 3, + shadowBlur: 12, + shadowOffsetX: 0, + shadowOffsetY: 4, + shadowColor: 'rgba(0, 0, 0, 0.1)', + size: 0, + space: 12 + } + // maxWidthPercent: 0.8 +}; diff --git a/packages/vtable/src/themes/theme.ts b/packages/vtable/src/themes/theme.ts index e3c41fc98..8380c611d 100644 --- a/packages/vtable/src/themes/theme.ts +++ b/packages/vtable/src/themes/theme.ts @@ -46,7 +46,7 @@ import { DEFAULTFONTFAMILY, DEFAULTFONTSIZE } from '../tools/global'; -import { getAxisStyle } from './component'; +import { defalutPoptipStyle, getAxisStyle } from './component'; //private symbol // const _ = getSymbol(); @@ -83,6 +83,8 @@ export class TableTheme implements ITableThemeDefine { private _selectionStyle: RequiredTableThemeDefine['selectionStyle'] | null = null; private _axisStyle: RequiredTableThemeDefine['axisStyle'] | null = null; + private _textPopTipStyle: RequiredTableThemeDefine['textPopTipStyle'] | null = null; + constructor(obj: PartialTableThemeDefine | ITableThemeDefine, superTheme: ITableThemeDefine) { this.internalTheme = { obj, @@ -632,6 +634,20 @@ export class TableTheme implements ITableThemeDefine { return this._axisStyle; } + get textPopTipStyle(): RequiredTableThemeDefine['textPopTipStyle'] { + if (!this._textPopTipStyle) { + const { obj, superTheme } = this.internalTheme; + const textPopTipStyle: RequiredTableThemeDefine['textPopTipStyle'] = ingoreNoneValueMerge( + {}, + defalutPoptipStyle, + superTheme.textPopTipStyle, + obj.textPopTipStyle + ); + this._textPopTipStyle = textPopTipStyle; + } + return this._textPopTipStyle; + } + hasProperty(names: string[]): boolean { const { obj, superTheme } = this.internalTheme; return hasThemeProperty(obj, names) || hasThemeProperty(superTheme, names); diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index ad6c6c245..7b1800e49 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -360,6 +360,9 @@ export interface BaseTableConstructorOptions { * 设置为 'none' 时, 表格滚动到顶部/底部时, 不再触发父容器滚动 * */ overscrollBehavior?: 'auto' | 'none'; + + // resize response time + resizeTime?: number; } export interface BaseTableAPI { /** 表格的行数 */ @@ -668,6 +671,8 @@ export interface BaseTableAPI { getBodyVisibleColRange: () => { colStart: number; colEnd: number }; /** 获取表格body部分的显示行号范围 */ getBodyVisibleRowRange: () => { rowStart: number; rowEnd: number }; + + hasCustomRenderOrLayout: () => boolean; } export interface ListTableProtected extends IBaseTableProtected { /** 表格数据 */ diff --git a/packages/vtable/src/ts-types/customElement.ts b/packages/vtable/src/ts-types/customElement.ts index 8406b35ad..457529ac3 100644 --- a/packages/vtable/src/ts-types/customElement.ts +++ b/packages/vtable/src/ts-types/customElement.ts @@ -18,7 +18,8 @@ interface baseElement { y: number | string | ((value: string) => number | string); dx?: number; dy?: number; - clickable?: boolean; + // clickable?: boolean; // @dispose + pickable?: boolean; cursor?: string; } export interface TextElement extends baseElement { diff --git a/packages/vtable/src/ts-types/theme.ts b/packages/vtable/src/ts-types/theme.ts index dc8a5118c..3ba7204ad 100644 --- a/packages/vtable/src/ts-types/theme.ts +++ b/packages/vtable/src/ts-types/theme.ts @@ -4,6 +4,7 @@ import type { ITextStyleOption } 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 ******* export type PartialTableThemeDefine = Partial; export type ThemeStyle = ITextStyleOption & { @@ -135,6 +136,9 @@ export interface ITableThemeDefine { topAxisStyle?: Omit; bottomAxisStyle?: Omit; }; + + // style for text pop tip + textPopTipStyle?: PopTipAttributes; } export type RequiredTableThemeDefine = Required;