Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@visactor/vtable-gantt",
"comment": "fix: return null for missing gantt task bars and support sub task rect lookup",
"type": "patch"
}
],
"packageName": "@visactor/vtable-gantt",
"email": "892739385@qq.com"
}
4 changes: 2 additions & 2 deletions docs/assets/api/en/GanttAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,12 @@ Get or set the horizontal scroll value to a specified position.
Get the position of the task bar. The position relative to the top-left corner of the Gantt chart.

```
getTaskBarRelativeRect:(index: number) =>{
getTaskBarRelativeRect:(index: number, sub_task_index?: number) =>{
left: number;
top: number;
width: number;
height: number;
}
} | null
```

## Events
Expand Down
4 changes: 2 additions & 2 deletions docs/assets/api/zh/GanttAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ const info = ganttInstance.getBaselineInfoByTaskListIndex(0);
获取任务条的位置。相对应甘特图表左上角的位置。

```
getTaskBarRelativeRect:(index: number) =>{
getTaskBarRelativeRect:(index: number, sub_task_index?: number) =>{
left: number;
top: number;
width: number;
height: number;
}
} | null
```

## Events
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import type { ColumnsDefine } from '@visactor/vtable';
import type { GanttConstructorOptions } from '../../src/index';
import { Gantt } from '../../src/index';
import { TasksShowMode, TaskType } from '../../src/ts-types';

const CONTAINER_ID = 'vTable';

const createStatusBar = () => {
const container = document.getElementById(CONTAINER_ID)!;
const status = document.createElement('div');
status.id = 'issue4778Status';
status.style.cssText = 'height: 32px; line-height: 32px; font-size: 13px; color: #333;';
status.textContent = 'Click "Check getTaskBarRelativeRect" to verify issue #4778.';

const button = document.createElement('button');
button.textContent = 'Check getTaskBarRelativeRect';
button.style.cssText = 'margin: 0 0 8px 8px;';
button.onclick = () => checkRects();

container.parentElement?.insertBefore(status, container);
status.appendChild(button);
};

const isValidRect = (rect: any) =>
rect &&
Number.isFinite(rect.left) &&
Number.isFinite(rect.top) &&
Number.isFinite(rect.width) &&
Number.isFinite(rect.height);

const checkRects = () => {
const ganttInstance = (window as any).ganttInstance as Gantt;
const status = document.getElementById('issue4778Status')!;
let emptyRect: any;
let firstChildRect: any;
let secondChildRect: any;

try {
emptyRect = ganttInstance.getTaskBarRelativeRect(1);
firstChildRect = ganttInstance.getTaskBarRelativeRect(0, [0, 0]);
secondChildRect = ganttInstance.getTaskBarRelativeRect(0, [0, 1]);
} catch (err) {
status.textContent = `FAIL | ${(err as Error).message}`;
return status.textContent;
}

const pass =
emptyRect === null &&
isValidRect(firstChildRect) &&
isValidRect(secondChildRect) &&
firstChildRect.left !== secondChildRect.left;

status.textContent = `${pass ? 'PASS' : 'FAIL'} | empty=${JSON.stringify(emptyRect)}, first=${JSON.stringify(
firstChildRect
)}, second=${JSON.stringify(secondChildRect)}`;
return status.textContent;
};

export function createTable() {
const records = [
{
id: 1,
title: 'Collapsed project with inline subtasks',
type: TaskType.PROJECT,
children: [
{ id: 11, title: 'Sub task A', start: '2024-07-01', end: '2024-07-04', progress: 30 },
{ id: 12, title: 'Sub task B', start: '2024-07-08', end: '2024-07-12', progress: 60 }
]
},
{
id: 2,
title: 'Empty task row'
}
];

const columns: ColumnsDefine = [
{ field: 'title', title: 'title', width: 220 },
{ field: 'start', title: 'start', width: 120 },
{ field: 'end', title: 'end', width: 120 },
{ field: 'progress', title: 'progress', width: 100 }
];

const option: GanttConstructorOptions = {
records,
taskListTable: {
columns,
tableWidth: 260
},
taskKeyField: 'id',
tasksShowMode: TasksShowMode.Project_Sub_Tasks_Inline,
taskBar: {
startDateField: 'start',
endDateField: 'end',
progressField: 'progress'
},
minDate: '2024-07-01',
maxDate: '2024-07-20',
timelineHeader: {
colWidth: 36,
scales: [{ unit: 'day', step: 1 }]
},
grid: {
verticalLine: {
lineWidth: 1,
lineColor: '#e1e4e8'
},
horizontalLine: {
lineWidth: 1,
lineColor: '#e1e4e8'
}
}
};

createStatusBar();
const ganttInstance = new Gantt(document.getElementById(CONTAINER_ID)!, option);
(window as any).ganttInstance = ganttInstance;
(window as any).issue4778Run = checkRects;
}
4 changes: 4 additions & 0 deletions packages/vtable-gantt/examples/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ export const menus = [
{
path: 'gantt',
name: 'gantt-issue-5162-sort-drag'
},
{
path: 'gantt',
name: 'issue-4778-taskbar-relative-rect'
}
// ]
// }
Expand Down
31 changes: 21 additions & 10 deletions packages/vtable-gantt/src/Gantt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1027,12 +1027,12 @@ export class Gantt extends EventTarget {
return this.records[taskShowIndex];
}

_refreshTaskBar(taskShowIndex: number, sub_task_index?: number) {
_refreshTaskBar(taskShowIndex: number, sub_task_index?: number | number[]) {
// this.taskListTableInstance.updateRecords([record], [index]);
this.scenegraph.taskBar.updateTaskBarNode(taskShowIndex, sub_task_index);
this.scenegraph.refreshRecordLinkNodes(
taskShowIndex,
undefined,
sub_task_index,
this.scenegraph.taskBar.getTaskBarNodeByIndex(taskShowIndex, sub_task_index) as GanttTaskBarNode
);
this.scenegraph.updateNextFrame();
Expand Down Expand Up @@ -1262,12 +1262,13 @@ export class Gantt extends EventTarget {
}
this._refreshSortedTaskBarsAfterRecordUpdate(recordIndex, index);
} else if (Array.isArray(sub_task_index)) {
this._updateRecordToListTable(taskRecord, sub_task_index);
// 递归更新父级project任务的时间范围
this.stateManager.updateProjectTaskTimes(sub_task_index);
}
}

_updateEndDateToTaskRecord(endDate: Date, index: number, sub_task_index?: number) {
_updateEndDateToTaskRecord(endDate: Date, index: number, sub_task_index?: number | number[]) {
const taskRecord = this.getRecordByIndex(index, sub_task_index);
const endDateField = this.parsedOptions.endDateField;
const dateFormat = this.parsedOptions.dateFormat ?? parseDateFormat(taskRecord[endDateField]);
Expand All @@ -1284,12 +1285,13 @@ export class Gantt extends EventTarget {
}
this._refreshSortedTaskBarsAfterRecordUpdate(recordIndex, index);
} else if (Array.isArray(sub_task_index)) {
this._updateRecordToListTable(taskRecord, sub_task_index);
// 递归更新父级project任务的时间范围
this.stateManager.updateProjectTaskTimes(sub_task_index);
}
}

_updateStartEndDateToTaskRecord(startDate: Date, endDate: Date, index: number, sub_task_index?: number) {
_updateStartEndDateToTaskRecord(startDate: Date, endDate: Date, index: number, sub_task_index?: number | number[]) {
const taskRecord = this.getRecordByIndex(index, sub_task_index);
const startDateField = this.parsedOptions.startDateField;
const endDateField = this.parsedOptions.endDateField;
Expand All @@ -1308,6 +1310,7 @@ export class Gantt extends EventTarget {
}
this._refreshSortedTaskBarsAfterRecordUpdate(recordIndex, index);
} else if (Array.isArray(sub_task_index)) {
this._updateRecordToListTable(taskRecord, sub_task_index);
// 递归更新父级project任务的时间范围
this.stateManager.updateProjectTaskTimes(sub_task_index);
}
Expand All @@ -1319,11 +1322,16 @@ export class Gantt extends EventTarget {
* @param index 对应的一定是左侧表格body的index
* @param sub_task_index 子任务的index, 当taskShowMode是sub_tasks_*模式时,会传入sub_task_index。如果是tasks_separate模式,sub_task_index传入undefined。
*/
_updateProgressToTaskRecord(progress: number, index: number, sub_task_index?: number) {
_updateProgressToTaskRecord(progress: number, index: number, sub_task_index?: number | number[]) {
const taskRecord = this.getRecordByIndex(index, sub_task_index);
const progressField = this.parsedOptions.progressField;
if (progressField) {
taskRecord[progressField] = progress;
if (Array.isArray(sub_task_index)) {
this._updateRecordToListTable(taskRecord, sub_task_index);
this._refreshTaskBar(index, sub_task_index);
return;
}
const recordIndex = this.getRecordIndexByTaskShowIndex(index);
this._updateRecordToListTable(taskRecord, Array.isArray(recordIndex) ? recordIndex : index);
if (!this._refreshSortedTaskBarsAfterRecordUpdate(recordIndex, index)) {
Expand Down Expand Up @@ -1353,11 +1361,11 @@ export class Gantt extends EventTarget {
* 如果TasksShowModes是 sub_tasks_*** 模式 则需要传入task_index和sub_task_index
*/
updateTaskRecord(record: any, task_index: number | number[]): void;
updateTaskRecord(record: any, task_index: number, sub_task_index: number): void;
updateTaskRecord(record: any, task_index: number | number[], sub_task_index?: number) {
updateTaskRecord(record: any, task_index: number, sub_task_index: number | number[]): void;
updateTaskRecord(record: any, task_index: number | number[], sub_task_index?: number | number[]) {
if (isValid(sub_task_index)) {
const index = typeof task_index === 'number' ? task_index : task_index[0];
this._updateRecordToListTable(record, [index, sub_task_index]);
this._updateRecordToListTable(record, Array.isArray(sub_task_index) ? sub_task_index : [index, sub_task_index]);
this._refreshTaskBar(index, sub_task_index);
return;
}
Expand Down Expand Up @@ -1637,8 +1645,11 @@ export class Gantt extends EventTarget {
this.stateManager.setScrollLeft(value);
}
/** 获取任务条的位置。相对应甘特图表左上角的位置。 */
getTaskBarRelativeRect(index: number) {
const taskBarNode = this.scenegraph.taskBar.getTaskBarNodeByIndex(index);
getTaskBarRelativeRect(index: number, sub_task_index?: number | number[]) {
const taskBarNode = this.scenegraph.taskBar.getTaskBarNodeByIndex(index, sub_task_index);
if (!taskBarNode) {
return null;
}
const left =
taskBarNode.attribute.x +
this.taskListTableInstance.tableNoFrameWidth +
Expand Down
2 changes: 1 addition & 1 deletion packages/vtable-gantt/src/scenegraph/gantt-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class GanttTaskBarNode extends Group {
textLabel?: IText;
declare name: string;
task_index: number;
sub_task_index?: number;
sub_task_index?: number | number[];
record?: any;
labelStyle?: ITaskBarLabelTextStyle;

Expand Down
7 changes: 6 additions & 1 deletion packages/vtable-gantt/src/scenegraph/scenegraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,12 @@ export class Scenegraph {
this.toolTip.hide();
}

refreshRecordLinkNodes(taskIndex: number, sub_task_index: number, target: GanttTaskBarNode, dy: number = 0) {
refreshRecordLinkNodes(
taskIndex: number,
sub_task_index: number | number[],
target: GanttTaskBarNode,
dy: number = 0
) {
const gantt: Gantt = this._gantt;
const record = gantt.getRecordByIndex(taskIndex, sub_task_index);
const vtable_gantt_linkedTo = record.vtable_gantt_linkedTo;
Expand Down
26 changes: 20 additions & 6 deletions packages/vtable-gantt/src/scenegraph/task-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ const LOCATE_ICON_BG_HOVER = '#4080ff';
const LOCATE_ICON_ARROW = '#4e5969';
const LOCATE_ICON_ARROW_HOVER = '#ffffff';

const isSameSubTaskIndex = (source?: number | number[], target?: number | number[]) => {
if (!isValid(target)) {
return true;
}

if (Array.isArray(source) || Array.isArray(target)) {
return (
Array.isArray(source) &&
Array.isArray(target) &&
source.length === target.length &&
source.every((value, index) => value === target[index])
);
}

return source === target;
};

export class TaskBar {
formatMilestoneText(text: string, record: any): string {
if (!text) {
Expand Down Expand Up @@ -524,7 +541,7 @@ export class TaskBar {
}
return { barGroupBox, baselineBar };
}
updateTaskBarNode(index: number, sub_task_index?: number) {
updateTaskBarNode(index: number, sub_task_index?: number | number[]) {
const taskbarGroup = this.getTaskBarNodeByIndex(index, sub_task_index);
if (taskbarGroup) {
this.barContainer.removeChild(taskbarGroup);
Expand Down Expand Up @@ -1054,16 +1071,13 @@ export class TaskBar {
this.selectedBorders[0].appendChild(line);
}

getTaskBarNodeByIndex(index: number, sub_task_index?: number): GanttTaskBarNode {
getTaskBarNodeByIndex(index: number, sub_task_index?: number | number[]): GanttTaskBarNode {
let c = this.barContainer.firstChild as GanttTaskBarNode;
if (!c) {
return null;
}
for (let i = 0; i < this.barContainer.childrenCount; i++) {
if (
c.task_index === index &&
(!isValid(sub_task_index) || (isValid(sub_task_index) && c.sub_task_index === sub_task_index))
) {
if (c.task_index === index && isSameSubTaskIndex(c.sub_task_index, sub_task_index)) {
return c;
}
c = c._next as GanttTaskBarNode;
Expand Down
Loading
Loading