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 ; 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 , end , hours ? ) { if ( typeof start === "string" ) { start = new Date ( start ) ; } if ( typeof end === "string" ) { end = new Date ( end ) ; } let days = this . calculateDiffDays ( start , end ) ; let width = days * this . cellWidth + days ; if ( hours ) { width = days * this . hourCellWidth * 24 + days * 24 ; } return width ; } private calculateBarLeft ( start , scale , hours ? ) { var left = 0 ; var hoursInDay = 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 ( ) { return `${ this . TASK_CACHE . length * this . rowHeight + this . rowHeight * 3 }px` ; } private calculateBarLeftDelta ( start , hours ? ) { var offset = 0 ; var hoursInDay = 24 ; var minutesInHour = 60 ; var secondsInHour = 3600 ; var startHours = 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 ) { try { var depth = treePath . split ( '/' ) . length ; if ( depth === 1 ) { return true ; } } catch ( err ) { return false ; } return false ; } public isChild ( treePath ) { if ( this . isParent ( treePath ) ) { return '0px' ; } return '20px' ; } /** Calculate the bar styles */ public calculateBar ( task , index , scale , hours ? ) { var barStyle = this . getBarStyle ( task . status ) ; 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 = "" ) { 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 = "" ) { 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 , percent ) { if ( typeof percent === "number" ) { if ( percent > 100 ) { percent = 100 ; } let progress = ( width / 100 ) * percent - 2 ; return `${ progress }px` ; } return `${ 0 }px` ; } /** Calculates the difference in two dates and returns number of days */ public calculateDiffDays ( start , end ) { try { let oneDay = 24 * 60 * 60 * 1000 ; let diffDays = Math . abs ( ( start . getTime ( ) - end . getTime ( ) ) / ( oneDay ) ) ; let days = diffDays ; return days ; } catch ( err ) { return 0 ; } } /** Calculates the difference in two dates and returns number of hours */ public calculateDuration ( task ) { 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)` ; } else if ( duration > 1 ) { return `${ Math . round ( duration ) } hr(s)` ; } else { let minutes = duration * 60 ; if ( minutes < 1 ) { return `${ Math . round ( minutes * 60 ) } second(s)` ; } return `${ Math . round ( minutes ) } min(s)` ; } } return '' ; } catch ( err ) { return '' ; } } calculateTotalDuration ( tasks ) { try { tasks = tasks . filter ( t => t . parentId === t . id ) ; 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 ; totalHours += duration ; } } if ( totalHours === 0 ) { return '' ; } if ( totalHours > 24 ) { return `${ Math . round ( totalHours / 24 ) } day(s)` ; } else if ( totalHours > 1 ) { return `${ Math . round ( totalHours ) } hr(s)` ; } else { let minutes = totalHours * 60 ; if ( minutes < 1 ) { return `${ Math . round ( minutes * 60 ) } second(s)` ; } return `${ Math . round ( minutes ) } min(s)` ; } } catch ( err ) { return '' ; } } /** Calculate the total percentage of a group of tasks */ calculateTotalPercentage ( node ) { var totalPercent = 0 ; var children = node . children ; if ( children . length > 0 ) { children . forEach ( ( child ) => { 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 , tasks ) { var children = tasks . filter ( ( task ) => { return task . parentId === parent . id && task . id != parent . id ; } ) ; var totalPercent = 0 ; if ( children . length > 0 ) { children . forEach ( ( child ) => { 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 = new Date ( ) , end = this . addDays ( start , 7 ) ) { let scale = [ ] ; 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 ) { 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 , days ) { let result = new Date ( date ) ; result . setDate ( result . getDate ( ) + days ) ; return result ; } public removeDays ( date , days ) { 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 ) { var start ; var end ; var dates = tasks . map ( ( task ) => { 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 ) { var hours = [ ] ; 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 , attribute ) { return parseInt ( document . defaultView . getComputedStyle ( element ) [ attribute ] , 10 ) ; } public calculateContainerWidth ( ) { this . windowInnerWidth = window . innerWidth ; let containerWidth = ( innerWidth - 18 ) ; return containerWidth ; } public calculateActivityContainerDimensions ( ) { var scrollWidth = 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 , ganttGridElem , ganttActivityAreaElem ) { let verticalScrollTop = verticalScrollElem . scrollTop ; let scroll = this . setScrollTop ; 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 ) { var merged = [ ] ; var groups = new GroupByPipe ( ) . transform ( tasks , ( task ) => { return [ task . treePath . split ( '/' ) [ 0 ] ] ; } ) ; return merged . concat . apply ( [ ] , groups ) ; } /** Create tree of data */ public transformData ( input ) { var output = [ ] ; for ( var i = 0 ; i < input . length ; i ++ ) { var chain = input [ i ] . id . split ( '/' ) ; var currentNode = output ; for ( var j = 0 ; j < chain . length ; j ++ ) { var wantedNode = chain [ j ] ; var lastNode = currentNode ; for ( var k = 0 ; k < currentNode . length ; k ++ ) { if ( currentNode [ k ] . name == wantedNode ) { currentNode = currentNode [ k ] . children ; break ; } } if ( lastNode == currentNode ) { var newNode = 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 , treeExpanded ) { var cachedTaskIds = this . TASK_CACHE . map ( ( task ) => { return task . id ; } ) ; var itemsToCache = [ ] ; if ( treeExpanded ) { tasks . filter ( ( task ) => { return cachedTaskIds . indexOf ( task . id ) === - 1 ; } ) . forEach ( ( task ) => { itemsToCache . push ( task ) ; } ) ; } else { tasks . filter ( ( task ) => { return cachedTaskIds . indexOf ( task . id ) === - 1 && task . treePath . split ( '/' ) . length === 1 ; } ) . forEach ( ( task ) => { itemsToCache . push ( task ) ; } ) ; } itemsToCache . forEach ( ( item ) => { 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 ) { return `_${ id }` ; } /** Set the scroll top property of a native DOM element */ private setScrollTop ( scrollTop , element ) { if ( element !== null && element !== undefined ) { element . scrollTop = scrollTop ; } } }