diff --git a/common/changes/@visactor/vtable/feature-text-fix_2025-04-25-13-09.json b/common/changes/@visactor/vtable/feature-text-fix_2025-04-25-13-09.json new file mode 100644 index 0000000000..a5f0e9598e --- /dev/null +++ b/common/changes/@visactor/vtable/feature-text-fix_2025-04-25-13-09.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "feat: add support for text not to be hidden #3802", + "type": "none" + } + ], + "packageName": "@visactor/vtable" +} \ No newline at end of file diff --git a/docs/assets/demo/en/gantt/gantt-orient.md b/docs/assets/demo/en/gantt/gantt-orient.md new file mode 100644 index 0000000000..9440e7d7b5 --- /dev/null +++ b/docs/assets/demo/en/gantt/gantt-orient.md @@ -0,0 +1,308 @@ +--- +category: examples +group: gantt +title: Gantt Style — Text Not Hidden +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/gantt-label-text.gif +link: gantt/introduction +option: Gantt#taskBar +--- + +# Gantt Style - Text Not Hidden + +This example demonstrates the style configuration of not hiding taskbar text. + +## Key Configuration + +- `orient` Text orientation relative to the taskbar. Optional values: `left`, `top`, `right`, `bottom`, representing the four directions respectively. +- `orientHandleWithOverflow` Specifies the taskbar text orientation when the label cannot fit within the taskbar. Ignored if `orient` is explicitly set. + +## Code Demo + +```javascript livedemo template=vtable +// import * as VTableGantt from '@visactor/vtable-gantt'; +let ganttInstance; +const records = [ + { + id: 1, + title: 'Software Development', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-24', + end: '2024-07-28', + progress: 100, + priority: 'P0' + }, + { + id: 2, + title: 'Project Feature Review', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-25', + end: '2024-07-27', + progress: 90, + priority: 'P0' + }, + { + id: 3, + title: 'Project Create', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-29', + end: '2024-07-31', + progress: 40, + priority: 'P1' + }, + { + id: 4, + title: 'Develop feature 1', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-30', + end: '2024-08-10', + progress: 30, + priority: 'P1' + }, + { + id: 5, + title: 'Determine project scope', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-08-01', + end: '2024-08-05', + progress: 60, + priority: 'P0' + }, + { + id: 6, + title: 'Project Status Review', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-08-06', + end: '2024-08-08', + progress: 10, + priority: 'P0' + }, + { + id: 7, + title: 'Feature Testing', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-08-09', + end: '2024-08-15', + progress: 70, + priority: 'P1' + }, + { + id: 8, + title: 'Project Complete', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-08-01', + end: '2024-08-10', + progress: 70, + priority: 'P0' + } +]; + +const columns = [ + { + field: 'title', + title: 'title', + width: 'auto', + sort: true, + tree: true, + editor: 'input' + }, + { + field: 'start', + title: 'start', + width: 'auto', + sort: true, + editor: 'date-input' + }, + { + field: 'end', + title: 'end', + width: 'auto', + sort: true, + editor: 'date-input' + }, + { + field: 'priority', + title: 'priority', + width: 'auto', + sort: true, + editor: 'input' + }, + { + field: 'progress', + title: 'progress', + width: 'auto', + sort: true, + headerStyle: { + borderColor: '#e1e4e8' + }, + style: { + borderColor: '#e1e4e8', + color: 'green' + }, + editor: 'input' + } +]; +const option = { + overscrollBehavior: 'none', + records, + taskListTable: { + columns, + tableWidth: 250, + minTableWidth: 100, + maxTableWidth: 600, + theme: { + headerStyle: { + borderColor: '#e1e4e8', + borderLineWidth: 1, + fontSize: 18, + fontWeight: 'bold', + color: 'red', + bgColor: '#EEF1F5' + }, + bodyStyle: { + borderColor: '#e1e4e8', + borderLineWidth: [1, 0, 1, 0], + fontSize: 16, + color: '#4D4D4D', + bgColor: '#FFF' + } + } + //rightFrozenColCount: 1 + }, + frame: { + outerFrameStyle: { + borderLineWidth: 2, + borderColor: '#e1e4e8', + cornerRadius: 8 + }, + verticalSplitLineMoveable: true, + verticalSplitLine: { + lineColor: '#e1e4e8', + lineWidth: 3 + }, + horizontalSplitLine: { + lineColor: '#e1e4e8', + lineWidth: 3 + } + }, + grid: { + weekendBackgroundColor: '#f8f8f8', + verticalLine: { + lineWidth: 1, + lineColor: '#e1e4e8' + }, + horizontalLine: { + lineWidth: 1, + lineColor: '#e1e4e8' + } + }, + headerRowHeight: 40, + rowHeight: 40, + taskBar: { + startDateField: 'start', + endDateField: 'end', + progressField: 'progress', + // resizable: false, + moveable: true, + hoverBarStyle: { + barOverlayColor: 'rgba(99, 144, 0, 0.4)' + }, + labelText: '{title} {progress}%', + labelTextStyle: { + fontFamily: 'Arial', + fontSize: 16, + textAlign: 'left', + textOverflow: 'visible', + orientHandleWithOverflow: 'right', + outsideColor: '#333333' + }, + barStyle: { + width: 20, + /** 任务条的颜色 */ + barColor: '#ee8800', + /** 已完成部分任务条的颜色 */ + completedBarColor: '#91e8e0', + /** 任务条的圆角 */ + cornerRadius: 8, + /** 任务条的边框 */ + borderLineWidth: 1, + /** 边框颜色 */ + borderColor: 'black' + }, + milestoneStyle: { + borderColor: 'red', + borderLineWidth: 1, + fillColor: 'green', + width: 15 + } + }, + timelineHeader: { + colWidth: 50, + backgroundColor: '#EEF1F5', + horizontalLine: { + lineWidth: 1, + lineColor: '#e1e4e8' + }, + verticalLine: { + lineWidth: 1, + lineColor: '#e1e4e8' + }, + scales: [ + { + unit: 'week', + step: 1, + startOfWeek: 'sunday', + format(date) { + return `Week ${date.dateIndex}`; + }, + style: { + fontSize: 20, + fontWeight: 'bold', + color: 'white', + strokeColor: 'black', + textAlign: 'right', + textBaseline: 'bottom', + backgroundColor: '#EEF1F5', + textStick: true + // padding: [0, 30, 0, 20] + } + }, + { + unit: 'day', + step: 1, + format(date) { + return date.dateIndex.toString(); + }, + style: { + fontSize: 20, + fontWeight: 'bold', + color: 'white', + strokeColor: 'black', + textAlign: 'right', + textBaseline: 'bottom', + backgroundColor: '#EEF1F5' + } + } + ] + }, + rowSeriesNumber: { + title: '行号', + dragOrder: true, + headerStyle: { + bgColor: '#EEF1F5', + borderColor: '#e1e4e8' + }, + style: { + borderColor: '#e1e4e8' + } + }, + scrollStyle: { + scrollRailColor: 'RGBA(246,246,246,0.5)', + visible: 'scrolling', + width: 6, + scrollSliderCornerRadius: 2, + scrollSliderColor: '#5cb85c' + } +}; +ganttInstance = new VTableGantt.Gantt(document.getElementById(CONTAINER_ID), option); +window['ganttInstance'] = ganttInstance; +``` diff --git a/docs/assets/demo/menu.json b/docs/assets/demo/menu.json index f1944b1de4..690228b7c5 100644 --- a/docs/assets/demo/menu.json +++ b/docs/assets/demo/menu.json @@ -342,6 +342,13 @@ "zh": "隐藏底部时间刻度", "en": "Gantt Hide Hour Scale" } + }, + { + "path": "gantt-orient", + "title": { + "zh": "甘特图样式-文字不隐藏", + "en": "Gantt Style —— Text Not Hidden" + } } ] }, diff --git a/docs/assets/demo/zh/gantt/gantt-orient.md b/docs/assets/demo/zh/gantt/gantt-orient.md new file mode 100644 index 0000000000..70884216c0 --- /dev/null +++ b/docs/assets/demo/zh/gantt/gantt-orient.md @@ -0,0 +1,308 @@ +--- +category: examples +group: gantt +title: 甘特图样式-文字不隐藏 +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/gantt-label-text.gif +link: gantt/introduction +option: Gantt#taskBar +--- + +# 甘特图样式—文字不隐藏 + +该示例展示了甘特图文字不隐藏的样式配置。 + +## 关键配置 + +- `orient` 相对于任务条文字方位位置,可选值:`left`, `top`, `right`, `bottom`,分别代表左、上、右、下四个方向 +- `orientHandleWithOverflow` 只有当文本在 taskbar 中容纳不下时,会根据该方位将文本显示在任务条旁边。当配置 `orient` 时,该配置无效 + +## 代码演示 + +```javascript livedemo template=vtable +// import * as VTableGantt from '@visactor/vtable-gantt'; +let ganttInstance; +const records = [ + { + id: 1, + title: 'Software Development', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-24', + end: '2024-07-28', + progress: 100, + priority: 'P0' + }, + { + id: 2, + title: 'Project Feature Review', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-25', + end: '2024-07-27', + progress: 90, + priority: 'P0' + }, + { + id: 3, + title: 'Project Create', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-29', + end: '2024-07-31', + progress: 40, + priority: 'P1' + }, + { + id: 4, + title: 'Develop feature 1', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-07-30', + end: '2024-08-10', + progress: 30, + priority: 'P1' + }, + { + id: 5, + title: 'Determine project scope', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-08-01', + end: '2024-08-05', + progress: 60, + priority: 'P0' + }, + { + id: 6, + title: 'Project Status Review', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-08-06', + end: '2024-08-08', + progress: 10, + priority: 'P0' + }, + { + id: 7, + title: 'Feature Testing', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-08-09', + end: '2024-08-15', + progress: 70, + priority: 'P1' + }, + { + id: 8, + title: 'Project Complete', + developer: 'liufangfang.jane@bytedance.com', + start: '2024-08-01', + end: '2024-08-10', + progress: 70, + priority: 'P0' + } +]; + +const columns = [ + { + field: 'title', + title: 'title', + width: 'auto', + sort: true, + tree: true, + editor: 'input' + }, + { + field: 'start', + title: 'start', + width: 'auto', + sort: true, + editor: 'date-input' + }, + { + field: 'end', + title: 'end', + width: 'auto', + sort: true, + editor: 'date-input' + }, + { + field: 'priority', + title: 'priority', + width: 'auto', + sort: true, + editor: 'input' + }, + { + field: 'progress', + title: 'progress', + width: 'auto', + sort: true, + headerStyle: { + borderColor: '#e1e4e8' + }, + style: { + borderColor: '#e1e4e8', + color: 'green' + }, + editor: 'input' + } +]; +const option = { + overscrollBehavior: 'none', + records, + taskListTable: { + columns, + tableWidth: 250, + minTableWidth: 100, + maxTableWidth: 600, + theme: { + headerStyle: { + borderColor: '#e1e4e8', + borderLineWidth: 1, + fontSize: 18, + fontWeight: 'bold', + color: 'red', + bgColor: '#EEF1F5' + }, + bodyStyle: { + borderColor: '#e1e4e8', + borderLineWidth: [1, 0, 1, 0], + fontSize: 16, + color: '#4D4D4D', + bgColor: '#FFF' + } + } + //rightFrozenColCount: 1 + }, + frame: { + outerFrameStyle: { + borderLineWidth: 2, + borderColor: '#e1e4e8', + cornerRadius: 8 + }, + verticalSplitLineMoveable: true, + verticalSplitLine: { + lineColor: '#e1e4e8', + lineWidth: 3 + }, + horizontalSplitLine: { + lineColor: '#e1e4e8', + lineWidth: 3 + } + }, + grid: { + weekendBackgroundColor: '#f8f8f8', + verticalLine: { + lineWidth: 1, + lineColor: '#e1e4e8' + }, + horizontalLine: { + lineWidth: 1, + lineColor: '#e1e4e8' + } + }, + headerRowHeight: 40, + rowHeight: 40, + taskBar: { + startDateField: 'start', + endDateField: 'end', + progressField: 'progress', + // resizable: false, + moveable: true, + hoverBarStyle: { + barOverlayColor: 'rgba(99, 144, 0, 0.4)' + }, + labelText: '{title} {progress}%', + labelTextStyle: { + fontFamily: 'Arial', + fontSize: 16, + textAlign: 'left', + textOverflow: 'visible', + orientHandleWithOverflow: 'right', + outsideColor: '#333333' + }, + barStyle: { + width: 20, + /** 任务条的颜色 */ + barColor: '#ee8800', + /** 已完成部分任务条的颜色 */ + completedBarColor: '#91e8e0', + /** 任务条的圆角 */ + cornerRadius: 8, + /** 任务条的边框 */ + borderLineWidth: 1, + /** 边框颜色 */ + borderColor: 'black' + }, + milestoneStyle: { + borderColor: 'red', + borderLineWidth: 1, + fillColor: 'green', + width: 15 + } + }, + timelineHeader: { + colWidth: 50, + backgroundColor: '#EEF1F5', + horizontalLine: { + lineWidth: 1, + lineColor: '#e1e4e8' + }, + verticalLine: { + lineWidth: 1, + lineColor: '#e1e4e8' + }, + scales: [ + { + unit: 'week', + step: 1, + startOfWeek: 'sunday', + format(date) { + return `Week ${date.dateIndex}`; + }, + style: { + fontSize: 20, + fontWeight: 'bold', + color: 'white', + strokeColor: 'black', + textAlign: 'right', + textBaseline: 'bottom', + backgroundColor: '#EEF1F5', + textStick: true + // padding: [0, 30, 0, 20] + } + }, + { + unit: 'day', + step: 1, + format(date) { + return date.dateIndex.toString(); + }, + style: { + fontSize: 20, + fontWeight: 'bold', + color: 'white', + strokeColor: 'black', + textAlign: 'right', + textBaseline: 'bottom', + backgroundColor: '#EEF1F5' + } + } + ] + }, + rowSeriesNumber: { + title: '行号', + dragOrder: true, + headerStyle: { + bgColor: '#EEF1F5', + borderColor: '#e1e4e8' + }, + style: { + borderColor: '#e1e4e8' + } + }, + scrollStyle: { + scrollRailColor: 'RGBA(246,246,246,0.5)', + visible: 'scrolling', + width: 6, + scrollSliderCornerRadius: 2, + scrollSliderColor: '#5cb85c' + } +}; +ganttInstance = new VTableGantt.Gantt(document.getElementById(CONTAINER_ID), option); +window['ganttInstance'] = ganttInstance; +``` diff --git a/docs/assets/option/en/common/gantt/task-bar-label-text-style.md b/docs/assets/option/en/common/gantt/task-bar-label-text-style.md index 1d93d5430c..1071aa2f06 100644 --- a/docs/assets/option/en/common/gantt/task-bar-label-text-style.md +++ b/docs/assets/option/en/common/gantt/task-bar-label-text-style.md @@ -1,6 +1,7 @@ {{ target: common-gantt-task-bar-label-text-style }} The definition of ITaskBarLabelTextStyle is: + ``` export interface ITaskBarLabelTextStyle { fontFamily?: string; @@ -10,5 +11,9 @@ export interface ITaskBarLabelTextStyle { textOverflow?: string; textBaseline?: 'alphabetic' | 'bottom' | 'middle' | 'top'; // Sets the vertical alignment of the text within the cell padding?: number | number[]; + /** Text orientation relative to the taskbar. Optional values: 'left', 'top', 'right', 'bottom', representing the four directions respectively. */ + orient?: 'left' | 'top' | 'right' | 'bottom'; + /** Specifies the taskbar text orientation when the label cannot fit within the taskbar. Ignored if `orient` is explicitly set. */ + orientHandleWithOverflow?: 'left' | 'top' | 'right' | 'bottom'; } ``` diff --git a/docs/assets/option/zh/common/gantt/task-bar-label-text-style.md b/docs/assets/option/zh/common/gantt/task-bar-label-text-style.md index fff8f7c7c4..393723a229 100644 --- a/docs/assets/option/zh/common/gantt/task-bar-label-text-style.md +++ b/docs/assets/option/zh/common/gantt/task-bar-label-text-style.md @@ -1,6 +1,7 @@ {{ target: common-gantt-task-bar-label-text-style }} -ITaskBarLabelTextStyle的定义为: +ITaskBarLabelTextStyle 的定义为: + ``` export interface ITaskBarLabelTextStyle { fontFamily?: string; @@ -10,5 +11,9 @@ export interface ITaskBarLabelTextStyle { textOverflow?: string; textBaseline?: 'alphabetic' | 'bottom' | 'middle' | 'top'; // 设置单元格内文字的垂直对齐方式 padding?: number | number[]; + /** 相对于任务条文字方位位置,可选值:'left', 'top', 'right', 'bottom',分别代表左、上、右、下四个方向 */ + orient?: 'left' | 'top' | 'right' | 'bottom'; + /** 只有当文本在 taskbar 中容纳不下时,会根据该方位将文本显示在任务条旁边。当配置 orient 时,该配置无效 */ + orientHandleWithOverflow?: 'left' | 'top' | 'right' | 'bottom'; } -``` \ No newline at end of file +``` diff --git a/packages/vtable-gantt/examples/gantt/gantt-milestone.ts b/packages/vtable-gantt/examples/gantt/gantt-milestone.ts index a6614db178..42d7c9354b 100644 --- a/packages/vtable-gantt/examples/gantt/gantt-milestone.ts +++ b/packages/vtable-gantt/examples/gantt/gantt-milestone.ts @@ -322,7 +322,6 @@ export function createTable() { }, headerRowHeight: 60, rowHeight: 40, - taskBar: { startDateField: 'start', endDateField: 'end', diff --git a/packages/vtable-gantt/src/gantt-helper.ts b/packages/vtable-gantt/src/gantt-helper.ts index 8677e06101..930d0e1071 100644 --- a/packages/vtable-gantt/src/gantt-helper.ts +++ b/packages/vtable-gantt/src/gantt-helper.ts @@ -261,10 +261,13 @@ export function initOptions(gantt: Gantt) { fontFamily: options?.taskBar?.labelTextStyle?.fontFamily ?? 'Arial', fontSize: options?.taskBar?.labelTextStyle?.fontSize ?? 20, color: options?.taskBar?.labelTextStyle?.color ?? '#F01', + outsideColor: options?.taskBar?.labelTextStyle?.outsideColor ?? '#333333', textAlign: options?.taskBar?.labelTextStyle?.textAlign ?? 'left', textBaseline: options?.taskBar?.labelTextStyle?.textBaseline ?? 'middle', padding: options?.taskBar?.labelTextStyle?.padding ?? [0, 0, 0, 10], - textOverflow: options?.taskBar?.labelTextStyle?.textOverflow + textOverflow: options?.taskBar?.labelTextStyle?.textOverflow, + orient: options?.taskBar?.labelTextStyle?.orient, + orientHandleWithOverflow: options?.taskBar?.labelTextStyle?.orientHandleWithOverflow }; gantt.parsedOptions.taskBarCustomLayout = options?.taskBar?.customLayout; gantt.parsedOptions.taskBarCreatable = diff --git a/packages/vtable-gantt/src/scenegraph/gantt-node.ts b/packages/vtable-gantt/src/scenegraph/gantt-node.ts index 166da1c3f6..1ae695b27f 100644 --- a/packages/vtable-gantt/src/scenegraph/gantt-node.ts +++ b/packages/vtable-gantt/src/scenegraph/gantt-node.ts @@ -1,16 +1,144 @@ import type { IRect, IText, IGroupGraphicAttribute } from '@visactor/vtable/es/vrender'; +import type { ITaskBarLabelTextStyle } from '../ts-types'; import { Group } from '@visactor/vtable/es/vrender'; +import { getTextPos } from '../gantt-helper'; +import { toBoxArray } from '../tools/util'; +import { isValid } from '@visactor/vutils'; +import { textMeasure } from '@visactor/vtable'; export class GanttTaskBarNode extends Group { clipGroupBox: Group; barRect?: IRect; progressRect?: IRect; textLabel?: IText; - name: string; + declare name: string; task_index: number; sub_task_index?: number; record?: any; + labelStyle?: ITaskBarLabelTextStyle; + + _lastWidth?: number; + _lastHeight?: number; + _lastX?: number; + _lastY?: number; constructor(attrs: IGroupGraphicAttribute) { super(attrs); + this._lastWidth = attrs.width; + this._lastHeight = attrs.height; + this._lastX = attrs.x; + this._lastY = attrs.y; + } + + /** + * 更新任务条文本标签的位置和样式 + * @description 根据任务条的大小和配置,更新文本标签的位置、对齐方式等属性 + * orient: 直接将文本显示在指定方位位置 + * orientHandleWithOverflow: 只有当文本溢出时才在指定方位显示,当配置了orient时此配置无效 + */ + updateTextPosition() { + if (!this.textLabel || !this.barRect) { + return; + } + + const labelStyle = this.labelStyle || {}; + const { + textAlign = 'left', + textBaseline = 'middle', + textOverflow, + color = '#333333', + outsideColor = '#333333', + padding: rawPadding = 8 + } = labelStyle; + + const padding = Array.isArray(rawPadding) ? rawPadding[3] : rawPadding; + const barWidth = this.barRect.attribute.width; + const barHeight = this.barRect.attribute.height; + + const fontSize = this.textLabel.attribute.fontSize || 12; + const fontFamily = this.textLabel.attribute.fontFamily || 'Arial'; + const text = String(this.textLabel.attribute.text || ''); + const textWidth = textMeasure.measureTextWidth(text, { fontSize, fontFamily }); + + const textFitsInBar = textWidth + padding * 2 <= barWidth; + const defaultPosition = getTextPos(toBoxArray(padding), textAlign, textBaseline, barWidth, barHeight); + const textPosition = + labelStyle.orient || + (!textFitsInBar && labelStyle.orientHandleWithOverflow ? labelStyle.orientHandleWithOverflow : null); + + this.textLabel.setAttribute('visible', true); + this.textLabel.setAttribute('textBaseline', textBaseline); + + if (textPosition) { + this.textLabel.parent?.removeChild(this.textLabel); + this.appendChild(this.textLabel); + this.textLabel.setAttribute('fill', outsideColor); + this.textLabel.setAttribute('ellipsis', undefined); + this.textLabel.setAttribute('maxLineWidth', undefined); + this.textLabel.setAttribute('zIndex', 10000); + this.setAttribute('zIndex', 10000); + + type Position = { + x: number; + y: number; + align: string; + baseline: string; + }; + type Positions = { + [key: string]: Position; + }; + + const positions: Positions = { + left: { + x: -padding, + y: barHeight / 2, + align: 'right', + baseline: 'middle' + }, + right: { + x: barWidth + padding, + y: barHeight / 2, + align: 'left', + baseline: 'middle' + }, + top: { + x: barWidth / 2, + y: -padding, + align: 'center', + baseline: 'bottom' + }, + bottom: { + x: barWidth / 2, + y: barHeight + padding, + align: 'center', + baseline: 'top' + } + }; + + const pos = positions[textPosition]; + if (pos) { + this.textLabel.setAttribute('x', pos.x); + this.textLabel.setAttribute('y', pos.y); + this.textLabel.setAttribute('textAlign', pos.align); + this.textLabel.setAttribute('textBaseline', pos.baseline); + } + } else { + this.textLabel.parent?.removeChild(this.textLabel); + this.clipGroupBox?.appendChild(this.textLabel); + this.textLabel.setAttribute('x', defaultPosition.x); + this.textLabel.setAttribute('y', defaultPosition.y); + this.textLabel.setAttribute('textAlign', textAlign); + this.textLabel.setAttribute('fill', color); + this.textLabel.setAttribute('maxLineWidth', barWidth - padding); + this.textLabel.setAttribute( + 'ellipsis', + textOverflow === 'clip' + ? '' + : textOverflow === 'ellipsis' + ? '...' + : isValid(textOverflow) + ? textOverflow + : undefined + ); + } } } diff --git a/packages/vtable-gantt/src/scenegraph/task-bar.ts b/packages/vtable-gantt/src/scenegraph/task-bar.ts index 92b806864e..b6cc4f80db 100644 --- a/packages/vtable-gantt/src/scenegraph/task-bar.ts +++ b/packages/vtable-gantt/src/scenegraph/task-bar.ts @@ -275,8 +275,13 @@ export class TaskBar { // dx: 12 + 4, // dy: this._scene._gantt.barLabelStyle.fontSize / 2 }); + barGroup.appendChild(label); barGroupBox.textLabel = label; + + barGroupBox.labelStyle = this._scene._gantt.parsedOptions.taskBarLabelStyle; + + barGroupBox.updateTextPosition(); } return barGroupBox; } @@ -288,6 +293,7 @@ export class TaskBar { const barGroup = this.initBar(index, sub_task_index); if (barGroup) { this.barContainer.insertInto(barGroup, index); //TODO + barGroup.updateTextPosition(); } } initHoverBarIcons() { diff --git a/packages/vtable-gantt/src/state/state-manager.ts b/packages/vtable-gantt/src/state/state-manager.ts index 80a777d67c..3f36b89a21 100644 --- a/packages/vtable-gantt/src/state/state-manager.ts +++ b/packages/vtable-gantt/src/state/state-manager.ts @@ -485,6 +485,7 @@ export class StateManager { if (this.selectedTaskBar.target !== target) { target.setAttribute('zIndex', 0); } + target.updateTextPosition(); this.moveTaskBar.target = null; this.moveTaskBar.deltaX = 0; this.moveTaskBar.deltaY = 0; @@ -604,8 +605,6 @@ export class StateManager { } gantt.scenegraph.updateNextFrame(); - - // } //#region 调整拖拽任务条的大小 startResizeTaskBar(target: Group, x: number, y: number, startOffsetY: number, onIconName: string) { @@ -704,6 +703,7 @@ export class StateManager { reCreateCustomNode(this._gantt, taskBarGroup, taskIndex, sub_task_index); taskBarGroup.setAttribute('zIndex', 0); } + taskBarGroup.updateTextPosition(); this.resizeTaskBar.resizing = false; this.resizeTaskBar.target = null; @@ -743,7 +743,6 @@ export class StateManager { ); this._gantt.scenegraph.updateNextFrame(); - // } //#endregion //#region 生成关联线的交互处理 @@ -1071,6 +1070,8 @@ function moveTaskBar(target: GanttTaskBarNode, dx: number, dy: number, state: St ]); } + target.updateTextPosition(); + state._gantt.scenegraph.refreshRecordLinkNodes(taskIndex, sub_task_index, target, dy); } @@ -1110,6 +1111,9 @@ function resizeTaskBar(target: GanttTaskBarNode, dx: number, newWidth: number, s textLabel.setAttribute('maxLineWidth', newWidth - TASKBAR_HOVER_ICON_WIDTH * 2); textLabel.setAttribute('x', position.x); } + + target.updateTextPosition(); + state.showTaskBarHover(); reCreateCustomNode(state._gantt, target, taskIndex, sub_task_index); diff --git a/packages/vtable-gantt/src/ts-types/gantt-engine.ts b/packages/vtable-gantt/src/ts-types/gantt-engine.ts index ef61d18a30..8eacb73cdf 100644 --- a/packages/vtable-gantt/src/ts-types/gantt-engine.ts +++ b/packages/vtable-gantt/src/ts-types/gantt-engine.ts @@ -237,12 +237,16 @@ export interface ITaskBarLabelTextStyle { fontFamily?: string; fontSize?: number; color?: string; + /** 当文字显示在任务条外侧时的颜色,默认为黑色 */ + outsideColor?: string; textAlign?: 'center' | 'end' | 'left' | 'right' | 'start'; // 设置单元格内文字的水平对齐方式 textOverflow?: string; textBaseline?: 'alphabetic' | 'bottom' | 'middle' | 'top'; // 设置单元格内文字的垂直对齐方式 padding?: number | number[]; - // /** 相对于任务条文字方位位置,可选值:'left', 'top', 'right', 'bottom',分别代表左、上、右、下四个方向 */ - // orient?: 'left', 'top', 'right', 'bottom'; + /** 相对于任务条文字方位位置,可选值:'left', 'top', 'right', 'bottom',分别代表左、上、右、下四个方向 */ + orient?: 'left' | 'top' | 'right' | 'bottom'; + /** 只有当文本在 taskbar 中容纳不下时,会根据该方位将文本显示在任务条旁边。当配置 orient 时,该配置无效 */ + orientHandleWithOverflow?: 'left' | 'top' | 'right' | 'bottom'; } export interface ITaskBarStyle { /** 任务条的颜色 */ diff --git a/packages/vtable/src/index.ts b/packages/vtable/src/index.ts index 0ecd316bcc..0b284d84b4 100644 --- a/packages/vtable/src/index.ts +++ b/packages/vtable/src/index.ts @@ -42,7 +42,7 @@ import * as CustomLayout from './render/layout'; import { updateCell } from './scenegraph/group-creater/cell-helper'; import { renderChart } from './scenegraph/graphic/contributions/chart-render-helper'; -import { restoreMeasureText, setCustomAlphabetCharSet } from './scenegraph/utils/text-measure'; +import { restoreMeasureText, setCustomAlphabetCharSet, textMeasure } from './scenegraph/utils/text-measure'; import type { BaseTableAPI } from './ts-types/base-table'; // import { container, loadCanvasPicker } from '@src/vrender'; @@ -93,6 +93,7 @@ export { GroupColumnDefine, TextAlignType, TextBaselineType, + textMeasure, themes, data, MousePointerCellEvent,