UNPKG

toway-vtable-gantt

Version:

canvas table width high performance

955 lines (948 loc) 375 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@visactor/vtable/es/vrender'), require('@visactor/vtable')) : typeof define === 'function' && define.amd ? define(['exports', '@visactor/vtable/es/vrender', '@visactor/vtable'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VTableGantt = {}, global.vrender$1, global.vtable$1)); })(this, (function (exports, vrender$1, vtable$1) { 'use strict'; var InteractionState; (function (InteractionState) { InteractionState["default"] = "default"; InteractionState["grabing"] = "grabing"; InteractionState["scrolling"] = "scrolling"; })(InteractionState || (InteractionState = {})); var DependencyType; (function (DependencyType) { DependencyType["FinishToStart"] = "finish_to_start"; DependencyType["StartToStart"] = "start_to_start"; DependencyType["FinishToFinish"] = "finish_to_finish"; DependencyType["StartToFinish"] = "start_to_finish"; })(DependencyType || (DependencyType = {})); var TasksShowMode; (function (TasksShowMode) { TasksShowMode["Tasks_Separate"] = "tasks_separate"; TasksShowMode["Sub_Tasks_Inline"] = "sub_tasks_inline"; TasksShowMode["Sub_Tasks_Separate"] = "sub_tasks_separate"; TasksShowMode["Sub_Tasks_Arrange"] = "sub_tasks_arrange"; TasksShowMode["Sub_Tasks_Compact"] = "sub_tasks_compact"; })(TasksShowMode || (TasksShowMode = {})); const GANTT_EVENT_TYPE = { SCROLL: 'scroll', CHANGE_DATE_RANGE: 'change_date_range', CLICK_TASK_BAR: 'click_task_bar', CONTEXTMENU_TASK_BAR: 'contextmenu_task_bar', MOUSEENTER_TASK_BAR: 'mouseenter_task_bar', MOUSELEAVE_TASK_BAR: 'mouseleave_task_bar', CREATE_TASK_SCHEDULE: 'create_task_schedule', CREATE_DEPENDENCY_LINK: 'create_dependency_link', DELETE_DEPENDENCY_LINK: 'delete_dependency_link', CLICK_DEPENDENCY_LINK_POINT: 'click_dependency_link_point', CONTEXTMENU_DEPENDENCY_LINK: 'contextmenu_dependency_link', CLICK_MARKLINE_CREATE: 'click_markline_create', CLICK_MARKLINE_CONTENT: 'click_markline_content' }; var index$1 = /*#__PURE__*/Object.freeze({ __proto__: null, get DependencyType () { return DependencyType; }, GANTT_EVENT_TYPE: GANTT_EVENT_TYPE, get InteractionState () { return InteractionState; }, get TasksShowMode () { return TasksShowMode; } }); const defaultTaskBarStyle = { barColor: 'blue', completedBarColor: 'gray', width: 30, cornerRadius: 3, borderWidth: 0, fontFamily: 'Arial', fontSize: 14 }; function setWidthToDefaultTaskBarStyle(width) { defaultTaskBarStyle.width = width; } const isNode = typeof window === 'undefined' || typeof window.window === 'undefined'; const DayTimes = 1000 * 60 * 60 * 24; function getDateIndexByX(x, gantt) { const totalX = x + gantt.stateManager.scroll.horizontalBarPos; const firstDateColWidth = gantt.getDateColWidth(0); const dateIndex = Math.floor((totalX - firstDateColWidth) / gantt.parsedOptions.timelineColWidth) + 1; return dateIndex; } function generateMarkLine(markLine) { if (!markLine) { return []; } if (markLine === true) { return [ { date: createDateAtMidnight().toLocaleDateString(), scrollToMarkLine: true, position: 'left', style: { lineColor: 'red', lineWidth: 1 } } ]; } else if (Array.isArray(markLine)) { return markLine.map((item, index) => { return { ...item, date: item.date, scrollToMarkLine: item.scrollToMarkLine, position: item.position ?? 'left', style: { lineColor: item.style?.lineColor || 'red', lineWidth: item.style?.lineWidth || 1, lineDash: item.style?.lineDash } }; }); } return [ { ...markLine, date: markLine.date, scrollToMarkLine: markLine.scrollToMarkLine ?? true, position: markLine.position ?? 'left', style: { lineColor: markLine.style?.lineColor || 'red', lineWidth: markLine.style?.lineWidth || 1, lineDash: markLine.style?.lineDash } } ]; } function getHorizontalScrollBarSize(scrollStyle) { if (scrollStyle?.hoverOn || (scrollStyle?.horizontalVisible && scrollStyle?.horizontalVisible === 'none') || (!scrollStyle?.horizontalVisible && scrollStyle?.visible === 'none')) { return 0; } return scrollStyle?.width ?? 7; } function getVerticalScrollBarSize(scrollStyle) { if (scrollStyle?.hoverOn || (scrollStyle?.verticalVisible && scrollStyle?.verticalVisible === 'none') || (!scrollStyle?.verticalVisible && scrollStyle?.visible === 'none')) { return 0; } return scrollStyle?.width ?? 7; } function initOptions(gantt) { const options = gantt.options; gantt.parsedOptions.tasksShowMode = options?.tasksShowMode ?? TasksShowMode.Tasks_Separate; gantt.parsedOptions.pixelRatio = options?.pixelRatio ?? 1; gantt.parsedOptions.rowHeight = options?.rowHeight ?? 40; gantt.parsedOptions.timelineColWidth = options?.timelineHeader?.colWidth ?? 60; gantt.parsedOptions.startDateField = options.taskBar?.startDateField ?? 'startDate'; gantt.parsedOptions.endDateField = options.taskBar?.endDateField ?? 'endDate'; gantt.parsedOptions.progressField = options.taskBar?.progressField ?? 'progress'; const { unit: minTimeUnit, startOfWeek, step } = gantt.parsedOptions.reverseSortedTimelineScales[0]; gantt.parsedOptions.minDate = options?.minDate ? getStartDateByTimeUnit(new Date(options.minDate), minTimeUnit, startOfWeek) : undefined; gantt.parsedOptions.maxDate = options?.maxDate ? getEndDateByTimeUnit(gantt.parsedOptions.minDate, new Date(options.maxDate), minTimeUnit, step) : undefined; gantt.parsedOptions._minDateTime = gantt.parsedOptions.minDate?.getTime(); gantt.parsedOptions._maxDateTime = gantt.parsedOptions.maxDate?.getTime(); gantt.parsedOptions.overscrollBehavior = options?.overscrollBehavior ?? 'auto'; gantt.parsedOptions.underlayBackgroundColor = options?.underlayBackgroundColor ?? '#FFF'; gantt.parsedOptions.scrollStyle = Object.assign({}, { scrollRailColor: 'rgba(100, 100, 100, 0.2)', scrollSliderColor: 'rgba(100, 100, 100, 0.5)', scrollSliderCornerRadius: 4, width: 10, visible: 'always', hoverOn: true, barToSide: false }, options?.scrollStyle); gantt.parsedOptions.timelineHeaderHorizontalLineStyle = options?.timelineHeader?.horizontalLine; gantt.parsedOptions.timelineHeaderVerticalLineStyle = options?.timelineHeader?.verticalLine; gantt.parsedOptions.timelineHeaderBackgroundColor = options?.timelineHeader?.backgroundColor; gantt.parsedOptions.timeLineHeaderRowHeights = []; gantt.parsedOptions.timelineHeaderStyles = []; for (let i = 0; i < gantt.parsedOptions.sortedTimelineScales.length ?? 0; i++) { const style = gantt.parsedOptions.sortedTimelineScales[i].style; gantt.parsedOptions.timelineHeaderStyles.push(Object.assign({ fontSize: 20, fontWeight: 'bold', textAlign: 'center', textBaseline: 'middle', color: '#000', backgroundColor: '#fff' }, style)); gantt.parsedOptions.timeLineHeaderRowHeights.push(gantt.parsedOptions.sortedTimelineScales[i].rowHeight ?? options?.headerRowHeight ?? 40); } gantt.parsedOptions.grid = Object.assign({}, options?.grid); setWidthToDefaultTaskBarStyle((gantt.parsedOptions.rowHeight * 3) / 4); gantt.parsedOptions.taskBarStyle = options?.taskBar?.barStyle && typeof options?.taskBar?.barStyle === 'function' ? options.taskBar.barStyle : Object.assign({}, defaultTaskBarStyle, options?.taskBar?.barStyle); gantt.parsedOptions.taskBarMilestoneStyle = Object.assign(typeof gantt.parsedOptions.taskBarStyle === 'function' ? {} : { width: gantt.parsedOptions.taskBarStyle.width, borderColor: gantt.parsedOptions.taskBarStyle.borderColor, borderLineWidth: gantt.parsedOptions.taskBarStyle.borderLineWidth ?? 1, fillColor: gantt.parsedOptions.taskBarStyle.barColor, cornerRadius: 0 }, options?.taskBar?.milestoneStyle); gantt.parsedOptions.taskBarMilestoneHypotenuse = gantt.parsedOptions.taskBarMilestoneStyle.width * Math.sqrt(2); gantt.parsedOptions.dateFormat = options?.dateFormat; gantt.parsedOptions.taskBarHoverStyle = Object.assign({ barOverlayColor: 'rgba(99, 144, 0, 0.4)' }, options?.taskBar?.hoverBarStyle); gantt.parsedOptions.taskBarSelectable = options?.taskBar?.selectable ?? true; gantt.parsedOptions.taskBarSelectedStyle = Object.assign(typeof gantt.parsedOptions.taskBarStyle === 'function' ? { shadowBlur: 6, shadowOffsetX: 0, shadowOffsetY: 0, borderLineWidth: 1 } : { shadowBlur: 6, shadowOffsetX: 0, shadowOffsetY: 0, shadowColor: gantt.parsedOptions.taskBarStyle.barColor, borderColor: gantt.parsedOptions.taskBarStyle.barColor, borderLineWidth: 1 }, options?.taskBar?.selectedBarStyle); gantt.parsedOptions.taskBarLabelText = options?.taskBar?.labelText ?? ''; gantt.parsedOptions.taskBarMoveable = options?.taskBar?.moveable ?? true; gantt.parsedOptions.moveTaskBarToExtendDateRange = options?.taskBar?.moveToExtendDateRange ?? true; gantt.parsedOptions.taskBarResizable = options?.taskBar?.resizable ?? true; gantt.parsedOptions.taskBarDragOrder = options?.taskBar?.dragOrder ?? true; gantt.parsedOptions.taskBarLabelStyle = { fontFamily: options?.taskBar?.labelTextStyle?.fontFamily ?? 'Arial', fontSize: options?.taskBar?.labelTextStyle?.fontSize ?? 20, color: options?.taskBar?.labelTextStyle?.color ?? '#F01', 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 }; gantt.parsedOptions.taskBarCustomLayout = options?.taskBar?.customLayout; gantt.parsedOptions.taskBarCreatable = options?.taskBar?.scheduleCreatable ?? !!(gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Separate || gantt.parsedOptions.tasksShowMode === TasksShowMode.Tasks_Separate); gantt.parsedOptions.taskBarCreationButtonStyle = Object.assign({ lineColor: 'rgb(99, 144, 0)', lineWidth: 1, lineDash: [5, 5], cornerRadius: 4, backgroundColor: '#FFF' }, options?.taskBar?.scheduleCreation?.buttonStyle); gantt.parsedOptions.taskBarCreationCustomLayout = options?.taskBar?.scheduleCreation?.customLayout; gantt.parsedOptions.taskBarCreationMaxWidth = options?.taskBar?.scheduleCreation?.maxWidth; gantt.parsedOptions.taskBarCreationMinWidth = options?.taskBar?.scheduleCreation?.minWidth; gantt.parsedOptions.outerFrameStyle = Object.assign({ borderColor: '#e1e4e8', borderLineWidth: 1, cornerRadius: 4 }, options.frame?.outerFrameStyle); gantt.parsedOptions.markLine = generateMarkLine(options?.markLine); if (gantt.parsedOptions.markLine?.length ?? 0) { if (gantt.parsedOptions.markLine?.every(item => item.scrollToMarkLine === undefined)) { gantt.parsedOptions.markLine[0].scrollToMarkLine = true; } if (gantt.parsedOptions.markLine?.find(item => item.scrollToMarkLine)) { gantt.parsedOptions.scrollToMarkLineDate = getStartDateByTimeUnit(new Date(gantt.parsedOptions.markLine?.find(item => item.scrollToMarkLine).date), minTimeUnit, startOfWeek); } } gantt.parsedOptions.verticalSplitLineHighlight = options.frame?.verticalSplitLineHighlight; gantt.parsedOptions.verticalSplitLine = Object.assign({ lineColor: gantt.parsedOptions.outerFrameStyle?.borderColor, lineWidth: gantt.parsedOptions.outerFrameStyle?.borderLineWidth }, options.frame?.verticalSplitLine); gantt.parsedOptions.horizontalSplitLine = options.frame?.horizontalSplitLine; gantt.parsedOptions.verticalSplitLineMoveable = options.frame?.verticalSplitLineMoveable; gantt.parsedOptions.taskKeyField = options.taskKeyField ?? 'id'; gantt.parsedOptions.dependencyLinks = options.dependency?.links ?? []; gantt.parsedOptions.dependencyLinkCreatable = options.dependency?.linkCreatable ?? false; gantt.parsedOptions.dependencyLinkSelectable = options.dependency?.linkSelectable ?? true; gantt.parsedOptions.dependencyLinkDeletable = options.dependency?.linkDeletable ?? false; gantt.parsedOptions.dependencyLinkLineStyle = Object.assign({ lineColor: 'red', lineWidth: 1 }, options.dependency?.linkLineStyle); gantt.parsedOptions.dependencyLinkSelectedLineStyle = Object.assign({ shadowBlur: 4, shadowOffset: 0, shadowColor: gantt.parsedOptions.dependencyLinkLineStyle.lineColor, lineColor: gantt.parsedOptions.dependencyLinkLineStyle.lineColor, lineWidth: gantt.parsedOptions.dependencyLinkLineStyle.lineWidth }, options?.dependency?.linkSelectedLineStyle); gantt.parsedOptions.dependencyLinkLineCreatePointStyle = Object.assign({ strokeColor: 'red', fillColor: 'white', radius: 5, strokeWidth: 1 }, options?.dependency?.linkCreatePointStyle); gantt.parsedOptions.dependencyLinkLineCreatingPointStyle = Object.assign({ strokeColor: 'red', fillColor: 'red', radius: 5, strokeWidth: 1 }, options?.dependency?.linkCreatingPointStyle); gantt.parsedOptions.dependencyLinkLineCreatingStyle = Object.assign({ lineColor: 'red', lineWidth: 1, lineDash: [5, 5] }, options?.dependency?.linkCreatingLineStyle); gantt.parsedOptions.eventOptions = options?.eventOptions; gantt.parsedOptions.keyboardOptions = options?.keyboardOptions; gantt.parsedOptions.markLineCreateOptions = options?.markLineCreateOptions; } function updateOptionsWhenScaleChanged(gantt) { const options = gantt.options; const { unit: minTimeUnit, startOfWeek, step } = gantt.parsedOptions.reverseSortedTimelineScales[0]; gantt.parsedOptions.minDate = getStartDateByTimeUnit(new Date(gantt.parsedOptions.minDate), minTimeUnit, startOfWeek); gantt.parsedOptions.maxDate = getEndDateByTimeUnit(gantt.parsedOptions.minDate, new Date(gantt.parsedOptions.maxDate), minTimeUnit, step); gantt.parsedOptions._minDateTime = gantt.parsedOptions.minDate?.getTime(); gantt.parsedOptions._maxDateTime = gantt.parsedOptions.maxDate?.getTime(); gantt.parsedOptions.timeLineHeaderRowHeights = []; gantt.parsedOptions.timelineHeaderStyles = []; for (let i = 0; i < gantt.parsedOptions.sortedTimelineScales.length ?? 0; i++) { const style = gantt.parsedOptions.sortedTimelineScales[i].style; gantt.parsedOptions.timelineHeaderStyles.push(Object.assign({ fontSize: 20, fontWeight: 'bold', textAlign: 'center', textBaseline: 'middle', color: '#000', backgroundColor: '#fff' }, style)); gantt.parsedOptions.timeLineHeaderRowHeights.push(gantt.parsedOptions.sortedTimelineScales[i].rowHeight ?? options?.headerRowHeight ?? 40); } } function generateTimeLineDate(currentDate, endDate, scale) { const { unit, step, format } = scale; const timelineDates = []; while (currentDate < endDate) { if (unit === 'day') { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const day = currentDate.getDate(); const end = createDateAtLastHour(new Date(year, month, day + step - 1), true); if (end.getTime() > endDate.getTime()) { end.setTime(endDate.getTime()); } const start = currentDate; const formattedDate = format?.({ dateIndex: day, startDate: start, endDate: end }); const columnTitle = formattedDate || day.toString(); const dayCellConfig = { days: Math.abs(end.getTime() - currentDate.getTime() + 1) / DayTimes, startDate: start, endDate: end, step, unit: 'day', title: columnTitle, dateIndex: day }; timelineDates.push(dayCellConfig); currentDate = new Date(year, month, day + step); } else if (unit === 'month') { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const end = createDateAtLastHour(new Date(year, month + step, 0), true); if (end.getTime() > endDate.getTime()) { end.setTime(endDate.getTime()); } const start = currentDate; const formattedDate = format?.({ dateIndex: month + 1, startDate: start, endDate: end }); const columnTitle = formattedDate || (month + 1).toString(); const dayCellConfig = { days: Math.abs(end.getTime() - currentDate.getTime() + 1) / DayTimes, startDate: start, step, unit: 'month', endDate: end, title: columnTitle, dateIndex: month + 1 }; timelineDates.push(dayCellConfig); currentDate = new Date(year, month + step, 1); } else if (unit === 'quarter') { const year = currentDate.getFullYear(); const quarter = Math.floor(currentDate.getMonth() / 3); const end = createDateAtLastHour(new Date(year, (quarter + step) * 3, 0), true); if (end.getTime() > endDate.getTime()) { end.setTime(endDate.getTime()); } const start = currentDate; const formattedDate = format?.({ dateIndex: quarter + 1, startDate: start, endDate: end }); const columnTitle = formattedDate || (quarter + 1).toString(); const dayCellConfig = { days: Math.abs(end.getTime() - currentDate.getTime() + 1) / (1000 * 60 * 60 * 24), startDate: start, step, unit: 'quarter', endDate: end, title: columnTitle, dateIndex: quarter + 1 }; timelineDates.push(dayCellConfig); currentDate = new Date(year, (quarter + step) * 3, 1); } else if (unit === 'year') { const year = currentDate.getFullYear(); const end = createDateAtLastHour(new Date(year + step - 1, 11, 31), true); if (end.getTime() > endDate.getTime()) { end.setTime(endDate.getTime()); } const start = currentDate; const formattedDate = format?.({ dateIndex: year, startDate: start, endDate: end }); const columnTitle = formattedDate || year.toString(); const dayCellConfig = { days: Math.abs(end.getTime() - currentDate.getTime() + 1) / DayTimes, startDate: start, endDate: end, step, unit: 'year', title: columnTitle, dateIndex: year }; timelineDates.push(dayCellConfig); currentDate = new Date(year + step, 0, 1); } else if (unit === 'week') { const startOfWeekSetting = scale.startOfWeek ?? 'monday'; let dayOfWeek = currentDate.getDay(); if (startOfWeekSetting === 'monday') { dayOfWeek = dayOfWeek === 0 ? 6 : dayOfWeek - 1; } const startOfWeek = createDateAtMidnight(currentDate); const dateEnd = createDateAtLastHour(currentDate.getTime() + (7 * step - dayOfWeek) * 24 * 60 * 60 * 1000 - 1, true); if (dateEnd > endDate) { dateEnd.setTime(endDate.getTime()); } const weekNumber = getWeekNumber(startOfWeek); const columnTitle = format?.({ dateIndex: weekNumber, startDate: startOfWeek, endDate: dateEnd }) || weekNumber.toString(); const dayCellConfig = { days: (dateEnd.getTime() - startOfWeek.getTime() + 1) / DayTimes, startDate: startOfWeek, endDate: dateEnd, step, unit: 'week', title: columnTitle, dateIndex: weekNumber }; timelineDates.push(dayCellConfig); currentDate.setTime(createDateAtMidnight(currentDate.getTime() + (7 * step - dayOfWeek) * 24 * 60 * 60 * 1000, true).getTime()); } else if (unit === 'hour') { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const day = currentDate.getDate(); const hour = currentDate.getHours(); const end = createDateAtLastMinute(new Date(year, month, day, hour + step - 1), true); if (end.getTime() > endDate.getTime()) { end.setTime(endDate.getTime()); } const start = currentDate; const formattedDate = format?.({ dateIndex: hour, startDate: start, endDate: end }); const columnTitle = formattedDate || hour.toString(); const dayCellConfig = { days: Math.abs(end.getTime() - currentDate.getTime() + 1) / DayTimes, startDate: start, endDate: end, step, unit: 'hour', title: columnTitle, dateIndex: currentDate.getHours() }; timelineDates.push(dayCellConfig); currentDate = new Date(year, month, day, hour + step); } else if (unit === 'minute') { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const day = currentDate.getDate(); const hour = currentDate.getHours(); const minute = currentDate.getMinutes(); const end = createDateAtLastSecond(new Date(year, month, day, hour, minute + step - 1), true); if (end.getTime() > endDate.getTime()) { end.setTime(endDate.getTime()); } const start = currentDate; const formattedDate = format?.({ dateIndex: minute, startDate: start, endDate: end }); const columnTitle = formattedDate || minute.toString(); const dayCellConfig = { days: Math.abs(end.getTime() - currentDate.getTime() + 1) / DayTimes, startDate: start, endDate: end, step, unit: 'minute', title: columnTitle, dateIndex: currentDate.getMinutes() }; timelineDates.push(dayCellConfig); currentDate = new Date(year, month, day, hour, minute + step); } else if (unit === 'second') { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const day = currentDate.getDate(); const hour = currentDate.getHours(); const minute = currentDate.getMinutes(); const second = currentDate.getSeconds(); const end = createDateAtLastMillisecond(new Date(year, month, day, hour, minute, second + step - 1), true); if (end.getTime() > endDate.getTime()) { end.setTime(endDate.getTime()); } const start = currentDate; const formattedDate = format?.({ dateIndex: second, startDate: start, endDate: end }); const columnTitle = formattedDate || second.toString(); const dayCellConfig = { days: Math.abs(end.getTime() - currentDate.getTime() + 1) / DayTimes, startDate: start, endDate: end, step, unit: 'second', title: columnTitle, dateIndex: currentDate.getSeconds() }; timelineDates.push(dayCellConfig); currentDate = new Date(year, month, day, hour, minute, second + step); } } return timelineDates; } function getTextPos(padding, textAlign, textBaseline, width, height) { let textX = padding[3] ?? 10; if (textAlign === 'right' || textAlign === 'end') { textX = width - 0 - (padding[1] ?? 10); } else if (textAlign === 'center') { textX = 0 + (width - 0 + (padding[3] ?? 10) - (padding[1] ?? 10)) / 2; } let textY = 0 + (padding[0] ?? 10); if (textBaseline === 'bottom' || textBaseline === 'alphabetic' || textBaseline === 'ideographic') { textY = height - 0 - (padding[2] ?? 10); } else if (textBaseline === 'middle') { textY = 0 + (height - 0 - (padding[0] ?? 10) - (padding[2] ?? 10)) / 2 + (padding[0] ?? 10); } return { x: textX, y: textY }; } function convertProgress(progress) { if (typeof progress === 'string') { progress = progress.replace('%', ''); progress = parseFloat(progress); } return Math.round(progress); } function createSplitLineAndResizeLine(gantt) { if (gantt.taskListTableInstance) { gantt.verticalSplitResizeLine = document.createElement('div'); gantt.verticalSplitResizeLine.style.position = 'absolute'; gantt.verticalSplitResizeLine.style.top = gantt.tableY + 'px'; gantt.verticalSplitResizeLine.style.left = (gantt.taskTableWidth ? gantt.taskTableWidth - 7 + gantt.parsedOptions.verticalSplitLine.lineWidth / 2 : 0) + 'px'; gantt.verticalSplitResizeLine.style.width = '14px'; gantt.verticalSplitResizeLine.style.height = gantt.drawHeight + 'px'; gantt.verticalSplitResizeLine.style.backgroundColor = 'rgba(0,0,0,0)'; gantt.verticalSplitResizeLine.style.zIndex = '100'; gantt.parsedOptions.verticalSplitLineMoveable && (gantt.verticalSplitResizeLine.style.cursor = 'col-resize'); gantt.verticalSplitResizeLine.style.userSelect = 'none'; gantt.verticalSplitResizeLine.style.opacity = '1'; const verticalSplitLine = document.createElement('div'); verticalSplitLine.style.position = 'absolute'; verticalSplitLine.style.top = '0px'; verticalSplitLine.style.left = `${(14 - gantt.parsedOptions.verticalSplitLine.lineWidth) / 2}px`; verticalSplitLine.style.width = gantt.parsedOptions.verticalSplitLine.lineWidth + 'px'; verticalSplitLine.style.height = '100%'; verticalSplitLine.style.backgroundColor = gantt.parsedOptions.verticalSplitLine.lineColor; verticalSplitLine.style.zIndex = '100'; verticalSplitLine.style.userSelect = 'none'; verticalSplitLine.style.pointerEvents = 'none'; verticalSplitLine.style.transition = 'background-color 0.3s'; gantt.verticalSplitResizeLine.appendChild(verticalSplitLine); if (gantt.parsedOptions.verticalSplitLineHighlight) { const highlightLine = document.createElement('div'); highlightLine.style.position = 'absolute'; highlightLine.style.top = '0px'; highlightLine.style.left = `${(14 - gantt.parsedOptions.verticalSplitLineHighlight.lineWidth ?? 2) / 2}px`; highlightLine.style.width = (gantt.parsedOptions.verticalSplitLineHighlight.lineWidth ?? 2) + 'px'; highlightLine.style.height = '100%'; highlightLine.style.backgroundColor = gantt.parsedOptions.verticalSplitLineHighlight.lineColor; highlightLine.style.zIndex = '100'; highlightLine.style.cursor = 'col-resize'; highlightLine.style.userSelect = 'none'; highlightLine.style.pointerEvents = 'none'; highlightLine.style.opacity = '0'; highlightLine.style.transition = 'background-color 0.3s'; gantt.verticalSplitResizeLine.appendChild(highlightLine); } gantt.container.appendChild(gantt.verticalSplitResizeLine); } } function updateSplitLineAndResizeLine(gantt) { if (gantt.verticalSplitResizeLine) { gantt.verticalSplitResizeLine.style.position = 'absolute'; gantt.verticalSplitResizeLine.style.top = gantt.tableY + 'px'; gantt.verticalSplitResizeLine.style.left = gantt.taskTableWidth ? `${gantt.taskTableWidth - 7 + gantt.parsedOptions.verticalSplitLine.lineWidth / 2}px` : '0px'; gantt.verticalSplitResizeLine.style.width = '14px'; gantt.verticalSplitResizeLine.style.height = gantt.drawHeight + 'px'; gantt.verticalSplitResizeLine.style.backgroundColor = 'rgba(0,0,0,0)'; gantt.verticalSplitResizeLine.style.zIndex = '100'; gantt.parsedOptions.verticalSplitLineMoveable && (gantt.verticalSplitResizeLine.style.cursor = 'col-resize'); gantt.verticalSplitResizeLine.style.userSelect = 'none'; gantt.verticalSplitResizeLine.style.opacity = '1'; const verticalSplitLine = gantt.verticalSplitResizeLine.childNodes[0]; verticalSplitLine.style.position = 'absolute'; verticalSplitLine.style.top = '0px'; verticalSplitLine.style.left = `${(14 - gantt.parsedOptions.verticalSplitLine.lineWidth) / 2}px`; verticalSplitLine.style.width = gantt.parsedOptions.verticalSplitLine.lineWidth + 'px'; verticalSplitLine.style.height = '100%'; verticalSplitLine.style.backgroundColor = gantt.parsedOptions.verticalSplitLine.lineColor; verticalSplitLine.style.zIndex = '100'; verticalSplitLine.style.userSelect = 'none'; verticalSplitLine.style.pointerEvents = 'none'; verticalSplitLine.style.transition = 'background-color 0.3s'; if (gantt.verticalSplitResizeLine.childNodes[1]) { const highlightLine = gantt.verticalSplitResizeLine.childNodes[1]; highlightLine.style.position = 'absolute'; highlightLine.style.top = '0px'; highlightLine.style.left = `${(14 - gantt.parsedOptions.verticalSplitLineHighlight.lineWidth ?? 2) / 2}px`; highlightLine.style.width = (gantt.parsedOptions.verticalSplitLineHighlight.lineWidth ?? 2) + 'px'; highlightLine.style.height = '100%'; highlightLine.style.backgroundColor = gantt.parsedOptions.verticalSplitLineHighlight.lineColor; highlightLine.style.zIndex = '100'; highlightLine.style.cursor = 'col-resize'; highlightLine.style.userSelect = 'none'; highlightLine.style.pointerEvents = 'none'; highlightLine.style.opacity = '0'; highlightLine.style.transition = 'background-color 0.3s'; } } } function findRecordByTaskKey(records, taskKeyField, taskKey, childrenField = 'children') { for (let i = 0; i < records.length; i++) { if ((Array.isArray(taskKey) && taskKey.length === 1 && records[i][taskKeyField] === taskKey[0]) || records[i][taskKeyField] === taskKey) { return { record: records[i], index: [i] }; } else if (records[i][childrenField]?.length) { if (Array.isArray(taskKey) && taskKey[0] === records[i][taskKeyField]) { const result = findRecordByTaskKey(records[i][childrenField], taskKeyField, taskKey.slice(1)); if (result) { result.index.unshift(i); return result; } } else if (!Array.isArray(taskKey)) { const result = findRecordByTaskKey(records[i][childrenField], taskKeyField, taskKey); if (result) { result.index.unshift(i); return result; } } } } } function clearRecordLinkInfos(records, childrenField = 'children') { for (let i = 0; i < records.length; i++) { if (records[i][childrenField]?.length) { clearRecordLinkInfos(records[i][childrenField], childrenField); } else { delete records[i].vtable_gantt_linkedTo; delete records[i].vtable_gantt_linkedFrom; } } } function clearRecordShowIndex(records, childrenField = 'children') { for (let i = 0; i < records.length; i++) { if (records[i][childrenField]?.length) { clearRecordShowIndex(records[i][childrenField], childrenField); } else { delete records[i].vtable_gantt_showIndex; } } } function getTaskIndexsByTaskY(y, gantt) { let task_index; let sub_task_index; if (gantt.taskListTableInstance) { const rowInfo = gantt.taskListTableInstance.getTargetRowAt(y + gantt.headerHeight); if (rowInfo) { const { row } = rowInfo; task_index = row - gantt.taskListTableInstance.columnHeaderLevelCount; const beforeRowsHeight = gantt.getRowsHeightByIndex(0, task_index - 1); if (gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Inline || gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Arrange || gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Compact || gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Separate) { sub_task_index = Math.floor((y - beforeRowsHeight) / gantt.parsedOptions.rowHeight); } } } else { task_index = Math.floor(y / gantt.parsedOptions.rowHeight); } return { task_index, sub_task_index }; } function computeRowsCountByRecordDateForCompact(gantt, record) { if (!record.children || record.children.length === 1) { if (record.children?.length === 1) { record.children[0].vtable_gantt_showIndex = 0; } else { record.vtable_gantt_showIndex = 0; } return 1; } const sortedChildren = record.children.slice().sort((a, b) => { const { startDate: aStartDate } = formatRecordDateConsiderHasHour(gantt, a); const { startDate: bStartDate } = formatRecordDateConsiderHasHour(gantt, b); return aStartDate.getTime() - bStartDate.getTime(); }); const rows = []; for (let i = 0; i <= sortedChildren.length - 1; i++) { const newRecord = sortedChildren[i]; const { startDate, endDate } = formatRecordDateConsiderHasHour(gantt, newRecord); let placed = false; for (let j = 0; j < rows.length; j++) { if (startDate.getTime() > rows[j]) { rows[j] = endDate.getTime(); placed = true; newRecord.vtable_gantt_showIndex = j; break; } } if (!placed) { rows.push(endDate.getTime()); newRecord.vtable_gantt_showIndex = rows.length - 1; } } return rows.length; } function isOverlapping(startDate, endDate, rowTasks, gantt) { return rowTasks.some(rowTask => { const { startDate: startDate2, endDate: endDate2 } = formatRecordDateConsiderHasHour(gantt, rowTask); return startDate <= endDate2 && startDate2 <= endDate; }); } function computeRowsCountByRecordDate(gantt, record) { if (!record.children || record.children.length === 1) { if (record.children?.length === 1) { record.children[0].vtable_gantt_showIndex = 0; } else { record.vtable_gantt_showIndex = 0; } return 1; } const rows = []; for (let i = 0; i <= record.children.length - 1; i++) { const newRecord = record.children[i]; const { startDate, endDate } = formatRecordDateConsiderHasHour(gantt, newRecord); let placed = false; for (let j = 0; j < rows.length; j++) { const rowTasks = record.children.filter((t) => t !== newRecord && t.vtable_gantt_showIndex === j); if (!isOverlapping(startDate, endDate, rowTasks, gantt)) { rows[j] = endDate.getTime(); placed = true; newRecord.vtable_gantt_showIndex = j; break; } } if (!placed) { rows.push(endDate.getTime()); newRecord.vtable_gantt_showIndex = rows.length - 1; } } return rows.length; } function formatRecordDateConsiderHasHour(gantt, record) { const { timeScaleIncludeHour, startDateField, endDateField } = gantt.parsedOptions; const startDate = record[startDateField]; const endDate = record[endDateField]; if (timeScaleIncludeHour) { return { startDate: createDateAtMidnight(startDate), endDate: createDateAtLastHour(endDate) }; } return { startDate: createDateAtMidnight(startDate, true), endDate: createDateAtLastHour(endDate, true) }; } function updateOptionsWhenRecordChanged(gantt) { const options = gantt.options; const { unit: minTimeUnit, startOfWeek } = gantt.parsedOptions.reverseSortedTimelineScales[0]; gantt.parsedOptions.markLine = generateMarkLine(options?.markLine); if (gantt.parsedOptions.markLine?.length ?? 0) { if (gantt.parsedOptions.markLine?.every(item => item.scrollToMarkLine === undefined)) { gantt.parsedOptions.markLine[0].scrollToMarkLine = true; } if (gantt.parsedOptions.markLine?.find(item => item.scrollToMarkLine)) { gantt.parsedOptions.scrollToMarkLineDate = getStartDateByTimeUnit(new Date(gantt.parsedOptions.markLine?.find(item => item.scrollToMarkLine).date), minTimeUnit, startOfWeek); } } gantt.parsedOptions.dependencyLinks = options.dependency?.links; } function updateOptionsWhenDateRangeChanged(gantt) { const options = gantt.options; const { unit: minTimeUnit, startOfWeek, step } = gantt.parsedOptions.reverseSortedTimelineScales[0]; gantt.parsedOptions.minDate = options?.minDate ? getStartDateByTimeUnit(new Date(options.minDate), minTimeUnit, startOfWeek) : undefined; gantt.parsedOptions.maxDate = options?.maxDate ? getEndDateByTimeUnit(gantt.parsedOptions.minDate, new Date(options.maxDate), minTimeUnit, step) : undefined; gantt.parsedOptions._minDateTime = gantt.parsedOptions.minDate?.getTime(); gantt.parsedOptions._maxDateTime = gantt.parsedOptions.maxDate?.getTime(); } function updateOptionsWhenMarkLineChanged(gantt) { const options = gantt.options; gantt.parsedOptions.markLine = generateMarkLine(options?.markLine); } function _getTaskInfoByXYForCreateSchedule(eventX, eventY, gantt) { const taskIndex = getTaskIndexsByTaskY(eventY - gantt.headerHeight + gantt.stateManager.scrollTop, gantt); const recordParent = gantt.getRecordByIndex(taskIndex.task_index); const dateIndex = getDateIndexByX(eventX, gantt); const dateRange = gantt.getDateRangeByIndex(dateIndex); if (recordParent?.children) { const taskIndex = getTaskIndexsByTaskY(eventY - gantt.headerHeight + gantt.stateManager.scrollTop, gantt); for (let i = 0; i < recordParent.children.length; i++) { const { startDate, endDate, taskDays, progress, taskRecord } = gantt.getTaskInfoByTaskListIndex(taskIndex.task_index, i); if (((gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Compact || gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Arrange) && taskRecord.vtable_gantt_showIndex === taskIndex.sub_task_index) || gantt.parsedOptions.tasksShowMode === TasksShowMode.Sub_Tasks_Inline) { if (dateRange.startDate.getTime() >= startDate.getTime() && dateRange.endDate.getTime() <= endDate.getTime()) { return { startDate, endDate, taskDays, progress, taskRecord }; } } } } } function getNodeClickPos(marklineIconNode, gantt) { const left = marklineIconNode.globalTransMatrix.e + gantt.taskListTableInstance.tableNoFrameWidth + gantt.taskListTableInstance.tableX + gantt.tableX; const top = marklineIconNode.globalTransMatrix.f; const width = marklineIconNode.attribute.width; const height = marklineIconNode.attribute.height; return { left, top, width, height }; } function judgeIfHasMarkLine(data, markLine) { const beginTime = data.startDate.getTime(); const endTime = data.endDate.getTime(); return markLine.some(item => { const marklineTime = new Date(item.date).getTime(); return marklineTime >= beginTime && marklineTime <= endTime; }); } function throttle(func, delay) { let timer = null; return function (...args) { if (!timer) { func.apply(this, args); timer = setTimeout(() => { timer = null; }, delay); } }; } function getTodayNearDay(dayOffset, format) { const today = new Date(); const todayTime = today.getTime(); const oneDayTime = 24 * 60 * 60 * 1000; const targetTime = todayTime + dayOffset * oneDayTime; const date = new Date(targetTime); if (format) { const year = date.getFullYear().toString(); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); format = format.replace('yyyy', year); format = format.replace('mm', month); format = format.replace('dd', day); return format; } return date; } function formatDate(date, format) { const year = date.getFullYear().toString(); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); format = format.replace('yyyy', year); format = format.replace('mm', month); format = format.replace('dd', day); if (format.length > 10) { const hour = date.getHours().toString().padStart(2, '0'); const minute = date.getMinutes().toString().padStart(2, '0'); const second = date.getSeconds().toString().padStart(2, '0'); format = format.replace('hh', hour); format = format.replace('mm', minute); format = format.replace('ss', second); } return format; } function validateDate(dateParts, format) { const yearIndex = format.indexOf('yyyy'); const monthIndex = format.indexOf('mm'); const dayIndex = format.indexOf('dd'); const dateYearIndex = yearIndex < monthIndex ? (yearIndex < dayIndex ? 0 : 1) : monthIndex < dayIndex ? 1 : 2; const dateMonthIndex = monthIndex < yearIndex ? (monthIndex < dayIndex ? 0 : 1) : monthIndex < dayIndex ? 1 : 2; const dateDayIndex = dayIndex < yearIndex ? (dayIndex < monthIndex ? 0 : 1) : dayIndex < monthIndex ? 1 : 2; const year = parseInt(dateParts[dateYearIndex], 10); const month = parseInt(dateParts[dateMonthIndex], 10) - 1; const day = parseInt(dateParts[dateDayIndex], 10); if (isNaN(year) || year < 1) { return false; } if (isNaN(month) || month < 0 || month > 11) { return false; } const daysInMonth = [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; if (isNaN(day) || day < 1 || day > daysInMonth[month]) { return false; } return true; } function validateTime(dateParts, format) { if (format.includes('hh') || format.includes('mm') || format.includes('ss')) { const timeIndex = format.indexOf('hh') > -1 ? format.indexOf('hh') : format.indexOf('HH'); const hour = parseInt(dateParts[timeIndex], 10); const minute = parseInt(dateParts[timeIndex + 1], 10); const second = dateParts.length > timeIndex + 2 ? parseInt(dateParts[timeIndex + 2], 10) : 0; if (isNaN(hour) || hour < 0 || hour > 23) { return false; } if (isNaN(minute) || minute < 0 || minute > 59) { return false; } if (isNaN(second) || second < 0 || second > 59) { return false; } } return true; } function isLeapYear(year) { return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; } function parseDateFormat(dateString) { const formats = [ 'yyyy-mm-dd', 'dd-mm-yyyy', 'mm/dd/yyyy', 'yyyy/mm/dd', 'dd/mm/yyyy', 'yyyy.mm.dd',