UNPKG

ng2-gantt

Version:
540 lines (459 loc) 19.3 kB
import { Injectable } from '@angular/core'; import { GanttConfig } from './gantt-config.service'; import { IBarStyle, Task, IScale, Zooming } from '../interfaces'; import { GroupByPipe } from '../../shared/pipes/groupBy.pipe'; @Injectable() export class GanttService { public rowHeight: number = 0; public hourCellWidth: number = 60; // change to 60 so minutes can been seen more easily public hoursCellWidth: number = this.hourCellWidth * 25; public cellWidth: number = 0; public windowInnerWidth: number = 0; public activityHeight: number = 0; public barHeight: number = 0; public barLineHeight: number = 0; public barTop: number = 0; public barMoveable: boolean = false; public gridWidth: number = 560; private barStyles: IBarStyle[] = [ { status: "information", backgroundColor: "rgb(18,195, 244)", border: "1px solid #2196F3", progressBackgroundColor: "#2196F3" }, { status: "warning", backgroundColor: "#FFA726", border: "1px solid #EF6C00", progressBackgroundColor: "#EF6C00" }, { status: "error", backgroundColor: "#EF5350", border: "1px solid #C62828", progressBackgroundColor: "#C62828" }, { status: "completed", backgroundColor: "#66BB6A", border: "1px solid #2E7D32", progressBackgroundColor: "#2E7D32" } ]; public TASK_CACHE: any[]; public TIME_SCALE: any[]; constructor() { let _ganttConfig = new GanttConfig(); this.rowHeight = _ganttConfig.rowHeight; this.cellWidth = _ganttConfig.cellWidth; this.activityHeight = _ganttConfig.activityHeight; this.barHeight = _ganttConfig.barHeight; this.barLineHeight = _ganttConfig.barLineHeight; this.barTop = _ganttConfig.rowHeight; this.barMoveable = _ganttConfig.barMoveable; } private calculateBarWidth(start: Date, end: Date, hours?: boolean): number { if (typeof start === "string") { start = new Date(start); } if (typeof end === "string") { end = new Date(end); } let days = this.calculateDiffDays(start, end); let width: number = days * this.cellWidth + days; if (hours) { width = days * this.hourCellWidth * 24 + days * 24; } return width; } private calculateBarLeft(start: Date, scale: any[], hours?: boolean): number { var left: number = 0; var hoursInDay: number = 24; if (start != null) { if (typeof start === "string") { start = new Date(); } for (var i = 0; i < scale.length; i++) { if (start.getDate() === scale[i].getDate()) { if (hours) { left = i * hoursInDay * this.hourCellWidth + hoursInDay * i + this.calculateBarLeftDelta(start, hours); } else { left = i * this.cellWidth + i + this.calculateBarLeftDelta(start, hours); } break; } } } return left; } /** Calculates the height of the gantt grid, activity and vertical scroll */ public calculateGanttHeight(): string { return `${this.TASK_CACHE.length * this.rowHeight + this.rowHeight * 3}px`; } private calculateBarLeftDelta(start: Date, hours?: boolean): number { var offset: number = 0; var hoursInDay: number = 24; var minutesInHour: number = 60; var secondsInHour: number = 3600; var startHours: number = start.getHours() + start.getMinutes() / minutesInHour + start.getSeconds() / secondsInHour; if (hours) { offset = this.hoursCellWidth / hoursInDay * startHours - startHours; } else { offset = this.cellWidth / hoursInDay * startHours; } return offset; } public isParent(treePath: string): boolean { try { var depth = treePath.split('/').length; if (depth === 1) { return true; } } catch (err) { return false; } return false; } public isChild(treePath: string) { if (this.isParent(treePath)) { return '0px'; } return '20px'; } /** Calculate the bar styles */ public calculateBar(task: any, index: number, scale: any, hours?: boolean) { var barStyle = this.getBarStyle(task.status); //console.log(this.barTop * index + 2) return { 'top': this.barTop * index + 2 + 'px', 'left': this.calculateBarLeft(task.start, scale, hours) + 'px', 'height': this.barHeight + 'px', 'line-height': this.barLineHeight + 'px', 'width': this.calculateBarWidth(task.start, task.end, hours) + 'px', 'background-color': barStyle["background-color"], 'border': barStyle["border"] } } /** Get the bar style based on task status */ private getBarStyle(taskStatus: string = ""): any { var style = {}; try { taskStatus = taskStatus.toLowerCase(); } catch (err) { taskStatus = ""; } switch (taskStatus) { case "information": style["background-color"] = this.barStyles[0].backgroundColor; style["border"] = this.barStyles[0].border; break; case "warning": style["background-color"] = this.barStyles[1].backgroundColor; style["border"] = this.barStyles[1].border; break; case "error": style["background-color"] = this.barStyles[2].backgroundColor; style["border"] = this.barStyles[2].border; break; case "completed": style["background-color"] = this.barStyles[3].backgroundColor; style["border"] = this.barStyles[3].border; break; default: style["background-color"] = "rgb(18,195, 244)"; style["border"] = "1px solid #2196F3"; break; } return style; } /** Get the progresss bar background colour based on task status */ public getBarProgressStyle(taskStatus: string = ""): any { var style = {}; try { taskStatus = taskStatus.toLowerCase(); } catch (err) { taskStatus = ""; } switch (taskStatus) { case "information": style["background-color"] = this.barStyles[0].progressBackgroundColor; break; case "warning": style["background-color"] = this.barStyles[1].progressBackgroundColor; break; case "error": style["background-color"] = this.barStyles[2].progressBackgroundColor; break; case "completed": style["background-color"] = this.barStyles[3].progressBackgroundColor; break; default: style["background-color"] = this.barStyles[0].progressBackgroundColor; break; } return style; } /** Calculates the bar progress width in pixels given task percent complete */ public calculateBarProgress(width: number, percent: number): string { if (typeof percent === "number") { if (percent > 100) { percent = 100; } let progress: number = (width / 100) * percent - 2; return `${progress}px`; } return `${0}px`; } /** Calculates the difference in two dates and returns number of days */ public calculateDiffDays(start: Date, end: Date): number { try { let oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds /ms let diffDays = Math.abs((start.getTime() - end.getTime()) / (oneDay)); let days = diffDays; // don't use Math.round as it will draw an incorrect bar return days; } catch (err) { return 0; } } /** Calculates the difference in two dates and returns number of hours */ public calculateDuration(task: any): string { try { if (task.start != null && task.end != null) { let oneHour = 60 * 60 * 1000; let diffHours = (Math.abs((task.start.getTime() - task.end.getTime()) / oneHour)); let duration = diffHours; if (duration > 24) { return `${Math.round(duration / 24)} day(s)`; // duration in days } else if (duration > 1) { return `${Math.round(duration)} hr(s)`; // duration in hours } else { let minutes = duration * 60; if (minutes < 1) { return `${Math.round(minutes * 60)} second(s)`; // duration in seconds } return `${Math.round(minutes)} min(s)` // duration in minutes } } return ''; } catch (err) { return ''; } } calculateTotalDuration(tasks: any[]): string { try { tasks = tasks.filter(t => t.parentId === t.id); // only calculate total duration with parent tasks let totalHours = 0; let oneHour = 60 * 60 * 1000; for (let i = 0; i < tasks.length; i++) { let start = tasks[i].start; let end = tasks[i].end; if (start != null && end != null) { let duration = Math.abs(tasks[i].end.getTime() - tasks[i].start.getTime()) / oneHour; // duration in hours totalHours += duration; } } if (totalHours === 0) { return ''; } if (totalHours > 24) { return `${Math.round(totalHours / 24)} day(s)`; // duration in days } else if (totalHours > 1) { return `${Math.round(totalHours)} hr(s)`; // duration in hours } else { let minutes = totalHours * 60; if (minutes < 1) { return `${Math.round(minutes * 60)} second(s)`; // duration in seconds } return `${Math.round(minutes)} min(s)` // duration in minutes } } catch (err) { return ''; } } /** Calculate the total percentage of a group of tasks */ calculateTotalPercentage(node: any): number { var totalPercent: number = 0; var children = node.children; if (children.length > 0) { children.forEach((child: any) => { totalPercent += isNaN(child.percentComplete) ? 0 : child.percentComplete; }); return Math.ceil(totalPercent / children.length); } else { return isNaN(node.percentComplete) ? 0 : node.percentComplete; } } /** Calculate the total percent of the parent task */ calculateParentTotalPercentage(parent: any, tasks: any[]) { var children = tasks.filter((task:any) => { return task.parentId === parent.id && task.id != parent.id }); // get only children tasks ignoring parent. var totalPercent: number = 0; if (children.length > 0) { children.forEach((child:any) => { totalPercent += isNaN(child.percentComplete) ? 0 : child.percentComplete; }); return Math.ceil(totalPercent / children.length); } else { return isNaN(parent.percentComplete) ? 0 : parent.percentComplete; } } /** Calculate the gantt scale range given the start and end date of tasks*/ public calculateScale(start: Date = new Date(), end: Date = this.addDays(start, 7)) { let scale: any[] = []; try { while (start.getTime() <= end.getTime()) { scale.push(start); start = this.addDays(start, 1); } return scale; } catch (err) { return scale; } } /** Determines whether given date is a weekend */ public isDayWeekend(date: Date): boolean { let day = date.getDay(); if (day === 6 || day === 0) { return true; } return false; } /** Add x number of days to a date object */ public addDays(date: Date, days: number): Date { let result = new Date(date); result.setDate(result.getDate() + days); return result; } //** Remove x number of days from a date object */ public removeDays(date: Date, days: number): Date { let result = new Date(date); result.setDate(result.getDate() - days); return result; } /** Calculates the grid scale for gantt based on tasks start and end dates */ public calculateGridScale(tasks: Task[]): IScale { var start: Date; var end: Date; var dates = tasks.map((task:any) => { return { start: new Date(task.start), end: new Date(task.end) } }); start = new Date(Math.min.apply(null, dates.map(function (t) { return t.start; }))); end = new Date(Math.max.apply(null, dates.map(function (t) { return t.end; }))); return { start: start, end: end } } /** Create an hours array for use in time scale component */ public getHours(cols: number): string[] { var hours: string[] = []; while (hours.length <= cols * 24) { for (var i = 0; i <= 23; i++) { if (i < 10) { hours.push('0' + i.toString()); } else { hours.push(i.toString()); } } } return hours; } public getComputedStyle(element: any, attribute: any) { return parseInt(document.defaultView.getComputedStyle(element)[attribute], 10); } //TODO(dale): determine whether this is needed public calculateContainerWidth(): number { this.windowInnerWidth = window.innerWidth; let containerWidth = (innerWidth - 18); return containerWidth; } public calculateActivityContainerDimensions(): any { var scrollWidth: number = 18; this.windowInnerWidth = window.innerWidth; let width = this.windowInnerWidth - this.gridWidth - scrollWidth; return { height: this.activityHeight, width: width }; } /** Set the vertical scroll top positions for gantt */ public scrollTop(verticalScrollElem: any, ganttGridElem: any, ganttActivityAreaElem: any) { let verticalScrollTop = verticalScrollElem.scrollTop; let scroll = this.setScrollTop; // debounce if (verticalScrollTop !== null && verticalScrollTop !== undefined) { setTimeout(function() { scroll(verticalScrollTop, ganttActivityAreaElem); scroll(ganttActivityAreaElem.scrollTop, ganttGridElem); }, 50); } } /** Group data by id , only supports one level*/ public groupData(tasks: any): any { var merged:any = []; var groups:any = new GroupByPipe().transform(tasks, (task:any) => { return [task.treePath.split('/')[0]] }); return merged.concat.apply([], groups); } /** Create tree of data */ public transformData(input: any): any { var output:any = []; for (var i = 0; i < input.length; i++) { var chain:any = input[i].id.split('/'); var currentNode:any = output; for (var j = 0; j < chain.length; j++) { var wantedNode:any = chain[j]; var lastNode:any = currentNode; for (var k = 0; k < currentNode.length; k++) { if (currentNode[k].name == wantedNode) { currentNode = currentNode[k].children; break; } } // If we couldn't find an item in this list of children // that has the right name, create one: if (lastNode == currentNode) { //TODO(dale): determine way to show percent complete on correct child var newNode:any = currentNode[k] = { name: wantedNode, percentComplete: input[i].percentComplete, start: input[i].start, end: input[i].end, children: [] }; currentNode = newNode.children; } } } return output; } /** Checks whether any new data needs to be added to task cache */ public doTaskCheck(tasks: any[], treeExpanded: boolean): boolean { var cachedTaskIds = this.TASK_CACHE.map((task:any) => { return task.id }); var itemsToCache: any[] = []; if (treeExpanded) { // push children and parent tasks that are not cached tasks.filter((task:any) => { return cachedTaskIds.indexOf(task.id) === -1 }).forEach((task:any) => { itemsToCache.push(task); }) } else { // only look at tasks that are not cached tasks.filter((task:any) => { return cachedTaskIds.indexOf(task.id) === -1 && task.treePath.split('/').length === 1 }).forEach((task:any) => { itemsToCache.push(task); }); } itemsToCache.forEach((item:any) => { this.TASK_CACHE.push(item); }); if (itemsToCache.length > 0) { return true; } return false; } /** Set a id prefix so CSS3 query selector can work with ids that contain numbers */ public setIdPrefix(id: string): string { return `_${id}`; } // /** Remove the id prefix to allow querying of data */ // public removeIdPrefix(id: string): string { // return id.substring(1, id.length - 1); // } /** Set the scroll top property of a native DOM element */ private setScrollTop(scrollTop: number, element: any): void { if (element !== null && element !== undefined) { element.scrollTop = scrollTop; } } }