import "./../style/gantt.less" ; import * as d3 from "d3" ; import * as _ from "lodash" ; import powerbi from "powerbi-visuals-api" ; type Selection < T1 , T2 = T1 > = d3 . Selection < any , T1 , any , T2 > ; import timeScale = d3 . ScaleTime ; import DataView = powerbi . DataView ; import IViewport = powerbi . IViewport ; import SortDirection = powerbi . SortDirection ; import DataViewValueColumn = powerbi . DataViewValueColumn ; import DataViewValueColumns = powerbi . DataViewValueColumns ; import DataViewMetadataColumn = powerbi . DataViewMetadataColumn ; import DataViewValueColumnGroup = powerbi . DataViewValueColumnGroup ; import PrimitiveValue = powerbi . PrimitiveValue ; import VisualObjectInstance = powerbi . VisualObjectInstance ; import DataViewCategoryColumn = powerbi . DataViewCategoryColumn ; import VisualObjectInstanceEnumeration = powerbi . VisualObjectInstanceEnumeration ; import DataViewObjectPropertyIdentifier = powerbi . DataViewObjectPropertyIdentifier ; import EnumerateVisualObjectInstancesOptions = powerbi . EnumerateVisualObjectInstancesOptions ; import VisualObjectInstanceEnumerationObject = powerbi . VisualObjectInstanceEnumerationObject ; import VisualObjectInstancesToPersist = powerbi . VisualObjectInstancesToPersist ; import IColorPalette = powerbi . extensibility . IColorPalette ; import ILocalizationManager = powerbi . extensibility . ILocalizationManager ; import IVisualEventService = powerbi . extensibility . IVisualEventService ; import VisualTooltipDataItem = powerbi . extensibility . VisualTooltipDataItem ; import ISelectionId = powerbi . visuals . ISelectionId ; import ISelectionIdBuilder = powerbi . visuals . ISelectionIdBuilder ; import IVisualHost = powerbi . extensibility . visual . IVisualHost ; import IVisual = powerbi . extensibility . visual . IVisual ; import VisualUpdateOptions = powerbi . extensibility . visual . VisualUpdateOptions ; import VisualConstructorOptions = powerbi . extensibility . visual . VisualConstructorOptions ; import * as SVGUtil from "powerbi-visuals-utils-svgutils" ; import SVGManipulations = SVGUtil . manipulation ; import ClassAndSelector = SVGUtil . CssConstants . ClassAndSelector ; import createClassAndSelector = SVGUtil . CssConstants . createClassAndSelector ; import IMargin = SVGUtil . IMargin ; import { pixelConverter as PixelConverter , valueType } from "powerbi-visuals-utils-typeutils" ; import PrimitiveType = valueType . PrimitiveType ; import ValueType = valueType . ValueType ; import { textMeasurementService as tms , valueFormatter as ValueFormatter } from "powerbi-visuals-utils-formattingutils" ; import TextProperties = tms . TextProperties ; import IValueFormatter = ValueFormatter . IValueFormatter ; import textMeasurementService = tms . textMeasurementService ; import { interactivityBaseService as interactivityService , interactivitySelectionService } from "powerbi-visuals-utils-interactivityutils" ; import appendClearCatcher = interactivityService . appendClearCatcher ; import IInteractiveBehavior = interactivityService . IInteractiveBehavior ; import IInteractivityService = interactivityService . IInteractivityService ; import createInteractivityService = interactivitySelectionService . createInteractivitySelectionService ; import { createTooltipServiceWrapper , TooltipEventArgs , ITooltipServiceWrapper , TooltipEnabledDataPoint } from "powerbi-visuals-utils-tooltiputils" ; import { ColorHelper } from "powerbi-visuals-utils-colorutils" ; import { legend as LegendModule , legendInterfaces , OpacityLegendBehavior , axisInterfaces , axisScale , axis as AxisHelper } from "powerbi-visuals-utils-chartutils" ; import ILegend = legendInterfaces . ILegend ; import LegendPosition = legendInterfaces . LegendPosition ; import LegendData = legendInterfaces . LegendData ; import createLegend = LegendModule . createLegend ; import LegendDataPoint = legendInterfaces . LegendDataPoint ; import IAxisProperties = axisInterfaces . IAxisProperties ; import { Behavior , BehaviorOptions } from "./behavior" ; import { Task , Line , LinearStop , MilestonePath , ExtraInformation , GanttViewModel , DaysOffDataForAddition , DayOffData , TaskTypeMetadata , TaskDaysOff , TaskTypes , GroupedTask , GanttCalculateScaleAndDomainOptions , GanttChartFormatters , MilestoneData , MilestoneDataPoint , Milestone } from "./interfaces" ; import { DurationHelper } from "./durationHelper" ; import { GanttColumns } from "./columns" ; import { GanttSettings , DateTypeSettings } from "./settings" ; import { drawNotRoundedRectByPath , drawRoundedRectByPath , drawCircle , drawDiamond , drawRectangle , isValidDate , isStringNotNullEmptyOrUndefined , hashCode } from "./utils" ; import { drawExpandButton , drawCollapseButton , drawMinusButton , drawPlusButton } from "./drawButtons" ; const PercentFormat = "0.00 %;-0.00 %;0.00 %" ; const ScrollMargin = 100 ; const MillisecondsInASecond = 1000 ; const MillisecondsInAMinute = 60 * MillisecondsInASecond ; const MillisecondsInAHour = 60 * MillisecondsInAMinute ; const MillisecondsInADay = 24 * MillisecondsInAHour ; const MillisecondsInWeek = 4 * MillisecondsInADay ; const MillisecondsInAMonth = 30 * MillisecondsInADay ; const MillisecondsInAYear = 365 * MillisecondsInADay ; const MillisecondsInAQuarter = MillisecondsInAYear / 4 ; const PaddingTasks = 5 ; const DaysInAWeekend = 2 ; const DaysInAWeek = 5 ; const DefaultChartLineHeight = 40 ; const TaskColumnName = "Task" ; const ParentColumnName = "Parent" ; const GanttDurationUnitType = [ "second" , "minute" , "hour" , "day" , ] ; export enum ResourceLabelPositions { Top = < any > "Top" , Right = < any > "Right" , Inside = < any > "Inside" } export enum DurationUnits { Second = < any > "second" , Minute = < any > "minute" , Hour = < any > "hour" , Day = < any > "day" } export enum DateTypes { Second = < any > "Second" , Minute = < any > "Minute" , Hour = < any > "Hour" , Day = < any > "Day" , Week = < any > "Week" , Month = < any > "Month" , Quarter = < any > "Quarter" , Year = < any > "Year" } export enum LabelsForDateTypes { Now = < any > "Now" , Today = < any > "Today" } export enum MilestoneShapeTypes { Rhombus = "Rhombus" , Circle = "Circle" , Square = "Square" } export class SortingOptions { isCustomSortingNeeded : boolean ; sortingDirection : SortDirection ; } module Selectors { export const ClassName = createClassAndSelector ( "gantt" ) ; export const Chart = createClassAndSelector ( "chart" ) ; export const ChartLine = createClassAndSelector ( "chart-line" ) ; export const Body = createClassAndSelector ( "gantt-body" ) ; export const AxisGroup = createClassAndSelector ( "axis" ) ; export const Domain = createClassAndSelector ( "domain" ) ; export const AxisTick = createClassAndSelector ( "tick" ) ; export const Tasks = createClassAndSelector ( "tasks" ) ; export const TaskGroup = createClassAndSelector ( "task-group" ) ; export const SingleTask = createClassAndSelector ( "task" ) ; export const TaskRect = createClassAndSelector ( "task-rect" ) ; export const TaskMilestone = createClassAndSelector ( "task-milestone" ) ; export const TaskProgress = createClassAndSelector ( "task-progress" ) ; export const TaskDaysOff = createClassAndSelector ( "task-days-off" ) ; export const TaskResource = createClassAndSelector ( "task-resource" ) ; export const TaskLabels = createClassAndSelector ( "task-labels" ) ; export const TaskLines = createClassAndSelector ( "task-lines" ) ; export const LabelLines = createClassAndSelector ( "label-lines" ) ; export const TaskLinesRect = createClassAndSelector ( "task-lines-rect" ) ; export const TaskTopLine = createClassAndSelector ( "task-top-line" ) ; export const CollapseAll = createClassAndSelector ( "collapse-all" ) ; export const CollapseAllArrow = createClassAndSelector ( "collapse-all-arrow" ) ; export const Label = createClassAndSelector ( "label" ) ; export const LegendItems = createClassAndSelector ( "legendItem" ) ; export const LegendTitle = createClassAndSelector ( "legendTitle" ) ; export const ClickableArea = createClassAndSelector ( "clickableArea" ) ; } module GanttRoles { export const Legend = "Legend" ; export const Task = "Task" ; export const StartDate = "StartDate" ; export const EndDate = "EndDate" ; export const Duration = "Duration" ; export const Completion = "Completion" ; export const Resource = "Resource" ; export const Tooltips = "Tooltips" ; export const Parent = "Parent" ; export const Milestones = "Milestones" ; } export class Gantt implements IVisual { private viewport : IViewport ; private colors : IColorPalette ; private colorHelper : ColorHelper ; private legend : ILegend ; private textProperties : TextProperties = { fontFamily : "wf_segoe-ui_normal" , fontSize : PixelConverter . toString ( 9 ) , } ; private static LegendPropertyIdentifier : DataViewObjectPropertyIdentifier = { objectName : "legend" , propertyName : "fill" } ; private static MilestonesPropertyIdentifier : DataViewObjectPropertyIdentifier = { objectName : "milestones" , propertyName : "fill" } ; private static TaskResourcePropertyIdentifier : DataViewObjectPropertyIdentifier = { objectName : "taskResource" , propertyName : "show" } ; private static CollapsedTasksPropertyIdentifier : DataViewObjectPropertyIdentifier = { objectName : "collapsedTasks" , propertyName : "list" } ; public static DefaultValues = { AxisTickSize : 6 , BarMargin : 2 , ResourceWidth : 100 , TaskColor : "#00B099" , TaskLineColor : "#ccc" , CollapseAllColor : "#000" , PlusMinusColor : "#5F6B6D" , CollapseAllTextColor : "#aaa" , MilestoneLineColor : "#ccc" , TaskCategoryLabelsRectColor : "#fafafa" , TaskLineWidth : 15 , IconMargin : 12 , IconHeight : 16 , IconWidth : 15 , ChildTaskLeftMargin : 25 , ParentTaskLeftMargin : 0 , DefaultDateType : "Week" , DateFormatStrings : { Second : "HH:mm:ss" , Minute : "HH:mm" , Hour : "HH:mm (dd)" , Day : "MMM dd" , Week : "MMM dd" , Month : "MMM yyyy" , Quarter : "MMM yyyy" , Year : "yyyy" } } ; private static DefaultGraphicWidthPercentage : number = 0.78 ; private static ResourceLabelDefaultDivisionCoefficient : number = 1.5 ; private static DefaultTicksLength : number = 50 ; private static DefaultDuration : number = 250 ; private static TaskLineCoordinateX : number = 15 ; private static AxisLabelClip : number = 40 ; private static AxisLabelStrokeWidth : number = 1 ; private static BarHeightMargin : number = 5 ; private static ChartLineHeightDivider : number = 4 ; private static ResourceWidthPadding : number = 10 ; private static TaskLabelsMarginTop : number = 15 ; private static ComplectionDefault : number = null ; private static ComplectionMax : number = 1 ; private static ComplectionMin : number = 0 ; private static ComplectionMaxInPercent : number = 100 ; private static MinTasks : number = 1 ; private static ChartLineProportion : number = 1.5 ; private static MilestoneTop : number = 0 ; private static DeviderForCalculatingPadding : number = 4 ; private static LabelTopOffsetForPadding : number = 0.5 ; private static DeviderForCalculatingCenter : number = 2 ; private static SubtasksLeftMargin : number = 10 ; private static NotCompletedTaskOpacity : number = .5 ; private static TaskOpacity : number = 1 ; private static RectRound : number = 7 ; private static get DefaultMargin ( ) : IMargin { return { top : 50 , right : 40 , bottom : 40 , left : 10 } ; } private margin : IMargin = Gantt . DefaultMargin ; private body : Selection < any > ; private ganttSvg : Selection < any > ; private viewModel : GanttViewModel ; private timeScale : timeScale < any , any > ; private collapseAllGroup : Selection < any > ; private axisGroup : Selection < any > ; private chartGroup : Selection < any > ; private taskGroup : Selection < any > ; private lineGroup : Selection < any > ; private lineGroupWrapper : Selection < any > ; private clearCatcher : Selection < any > ; private ganttDiv : Selection < any > ; private behavior : Behavior ; private interactivityService : IInteractivityService < Task | LegendDataPoint > ; private eventService : IVisualEventService ; private tooltipServiceWrapper : ITooltipServiceWrapper ; private host : IVisualHost ; private localizationManager : ILocalizationManager ; private isInteractiveChart : boolean = false ; private groupTasksPrevValue : boolean = false ; private collapsedTasks : string [ ] = [ ] ; private collapseAllFlag : "data-is-collapsed" ; private parentLabelOffset : number = 5 ; private groupLabelSize : number = 25 ; private secondExpandAllIconOffset : number = 7 ; private hasNotNullableDates : boolean = false ; constructor ( options ) { if ( window . location !== window . parent . location ) { require ( "core-js/stable" ) ; } this . init ( options ) ; } private init ( options ) { this . host = options . host ; this . localizationManager = this . host . createLocalizationManager ( ) ; this . colors = options . host . colorPalette ; this . colorHelper = new ColorHelper ( this . colors ) ; this . body = d3 . select ( options . element ) ; this . tooltipServiceWrapper = createTooltipServiceWrapper ( this . host . tooltipService , options . element ) ; this . behavior = new Behavior ( ) ; this . interactivityService = createInteractivityService ( this . host ) ; this . eventService = options . host . eventService ; this . createViewport ( options . element ) ; } /**
     * Create the viewport area of the gantt chart
     */ private createViewport ( element ) { let self = this ; const isHighContrast = this . colorHelper . isHighContrast ; const axisBackgroundColor = this . colorHelper . getThemeColor ( ) ; this . ganttDiv = this . body . append ( "div" ) . classed ( Selectors . Body . className , true ) ; this . ganttSvg = this . ganttDiv . append ( "svg" ) . classed ( Selectors . ClassName . className , true ) ; this . clearCatcher = appendClearCatcher ( this . ganttSvg ) ; this . chartGroup = this . ganttSvg . append ( "g" ) . classed ( Selectors . Chart . className , true ) ; this . taskGroup = this . chartGroup . append ( "g" ) . classed ( Selectors . Tasks . className , true ) ; this . taskGroup = this . chartGroup . append ( "g" ) . classed ( Selectors . Tasks . className , true ) ; this . axisGroup = this . ganttSvg . append ( "g" ) . classed ( Selectors . AxisGroup . className , true ) ; this . axisGroup . append ( "rect" ) . attr ( "width" , "100%" ) . attr ( "y" , "-20" ) . attr ( "height" , "40px" ) . attr ( "fill" , axisBackgroundColor ) ; this . lineGroup = this . ganttSvg . append ( "g" ) . classed ( Selectors . TaskLines . className , true ) ; this . lineGroupWrapper = this . lineGroup . append ( "rect" ) . classed ( Selectors . TaskLinesRect . className , true ) . attr ( "height" , "100%" ) . attr ( "width" , "0" ) . attr ( "fill" , axisBackgroundColor ) . attr ( "y" , this . margin . top ) ; this . lineGroup . append ( "rect" ) . classed ( Selectors . TaskTopLine . className , true ) . attr ( "width" , "100%" ) . attr ( "height" , 1 ) . attr ( "y" , this . margin . top ) . attr ( "fill" , this . colorHelper . getHighContrastColor ( "foreground" , Gantt . DefaultValues . TaskLineColor ) ) ; this . collapseAllGroup = this . lineGroup . append ( "g" ) . classed ( Selectors . CollapseAll . className , true ) ; const interactiveBehavior = this . colorHelper . isHighContrast ? new OpacityLegendBehavior ( ) : null ; this . legend = createLegend ( element , this . isInteractiveChart , this . interactivityService , true , LegendPosition . Top , interactiveBehavior ) ; this . ganttDiv . on ( "scroll" , function ( evt ) { if ( self . viewModel ) { const taskLabelsWidth = self . viewModel . settings . taskLabels . show ? self . viewModel . settings . taskLabels . width : 0 ; self . axisGroup . attr ( "transform" , SVGManipulations . translate ( taskLabelsWidth + self . margin . left + Gantt . SubtasksLeftMargin , Gantt . TaskLabelsMarginTop + this . scrollTop ) ) ; self . lineGroup . attr ( "transform" , SVGManipulations . translate ( this . scrollLeft , 0 ) ) . attr ( "height" , 20 ) ; } } , false ) ; } /**
     * Clear the viewport area
     */ private clearViewport ( ) { this . ganttDiv . style ( "height" , 0 ) . style ( "width" , 0 ) ; this . body . selectAll ( Selectors . LegendItems . selectorName ) . remove ( ) ; this . body . selectAll ( Selectors . LegendTitle . selectorName ) . remove ( ) ; this . axisGroup . selectAll ( Selectors . AxisTick . selectorName ) . remove ( ) ; this . axisGroup . selectAll ( Selectors . Domain . selectorName ) . remove ( ) ; this . collapseAllGroup . selectAll ( Selectors . CollapseAll . selectorName ) . remove ( ) ; this . lineGroup . selectAll ( Selectors . TaskLabels . selectorName ) . remove ( ) ; this . lineGroup . selectAll ( Selectors . Label . selectorName ) . remove ( ) ; this . chartGroup . selectAll ( Selectors . ChartLine . selectorName ) . remove ( ) ; this . chartGroup . selectAll ( Selectors . TaskGroup . selectorName ) . remove ( ) ; this . chartGroup . selectAll ( Selectors . SingleTask . selectorName ) . remove ( ) ; } /**
     * Update div container size to the whole viewport area
     */ private updateChartSize ( ) { this . ganttDiv . style ( "height" , PixelConverter . toString ( this . viewport . height ) ) . style ( "width" , PixelConverter . toString ( this . viewport . width ) ) ; } param column param name private static hasRole ( column , name ) { return column . roles && column . roles [ name ] ; } param task param formatters param durationUnit public static getTooltipInfo ( task , formatters , durationUnit , localizationManager , isEndDateFillled ) { let tooltipDataArray = [ ] ; if ( task . taskType ) { tooltipDataArray . push ( { displayName : localizationManager . getDisplayName ( "Role_Legend" ) , value : task . taskType } ) ; } tooltipDataArray . push ( { displayName : localizationManager . getDisplayName ( "Role_Task" ) , value : task . name } ) ; if ( task . start && ! isNaN ( task . start . getDate ( ) ) ) { tooltipDataArray . push ( { displayName : localizationManager . getDisplayName ( "Role_StartDate" ) , value : formatters . startDateFormatter . format ( task . start ) } ) ; } if ( _ . isEmpty ( task . Milestones ) && task . end && ! isNaN ( task . end . getDate ( ) ) ) { tooltipDataArray . push ( { displayName : localizationManager . getDisplayName ( "Role_EndDate" ) , value : formatters . startDateFormatter . format ( task . end ) } ) ; } if ( _ . isEmpty ( task . Milestones ) && task . duration && ! isEndDateFillled ) { const durationLabel = DurationHelper . generateLabelForDuration ( task . duration , durationUnit , localizationManager ) ; tooltipDataArray . push ( { displayName : localizationManager . getDisplayName ( "Role_Duration" ) , value : durationLabel } ) ; } if ( task . completion ) { tooltipDataArray . push ( { displayName : localizationManager . getDisplayName ( "Role_Completion" ) , value : formatters . completionFormatter . format ( task . completion ) } ) ; } if ( task . resource ) { tooltipDataArray . push ( { displayName : localizationManager . getDisplayName ( "Role_Resource" ) , value : task . resource } ) ; } if ( task . tooltipInfo && task . tooltipInfo . length ) { tooltipDataArray . push ( ... task . tooltipInfo ) ; } task . extraInformation . map ( tooltip => { if ( typeof tooltip . value === "string" ) { return tooltip ; } const value = tooltip . value ; if ( isNaN ( Date . parse ( value ) ) || typeof value === "number" ) { tooltip . value = value . toString ( ) ; } else { tooltip . value = formatters . startDateFormatter . format ( value ) ; } return tooltip ; } ) . forEach ( tooltip => tooltipDataArray . push ( tooltip ) ) ; tooltipDataArray . filter ( x => x . value && typeof x . value !== "string" ) . forEach ( tooltip => tooltip . value = tooltip . value . toString ( ) ) ; return tooltipDataArray ; } param dataView private static isChartHasTask ( dataView ) { if ( dataView . metadata && dataView . metadata . columns ) { for ( let column of dataView . metadata . columns ) { if ( Gantt . hasRole ( column , GanttRoles . Task ) ) { return true ; } } } return false ; } param dataView param cultureSelector private static getFormatters ( dataView , settings , cultureSelector ) { if ( ! dataView || ! dataView . metadata || ! dataView . metadata . columns ) { return null ; } let dateFormat = "d" ; for ( let dvColumn of dataView . metadata . columns ) { if ( Gantt . hasRole ( dvColumn , GanttRoles . StartDate ) ) { dateFormat = dvColumn . format ; } } if ( cultureSelector ) { dateFormat = null ; } if ( ! settings . tooltipConfig . dateFormat ) { settings . tooltipConfig . dateFormat = dateFormat ; } if ( settings . tooltipConfig . dateFormat && settings . tooltipConfig . dateFormat !== dateFormat ) { dateFormat = settings . tooltipConfig . dateFormat ; } return < GanttChartFormatters > { startDateFormatter : ValueFormatter . create ( { format : dateFormat , cultureSelector } ) , completionFormatter : ValueFormatter . create ( { format : PercentFormat , value : 1 , allowFormatBeautification : true } ) } ; } private static createLegend ( host , colorPalette , settings , taskTypes , useDefaultColor ) { const colorHelper = new ColorHelper ( colorPalette , Gantt . LegendPropertyIdentifier ) ; const legendData = { fontSize : settings . legend . fontSize , dataPoints : [ ] , title : settings . legend . showTitle ? ( settings . legend . titleText || taskTypes . typeName ) : null , labelColor : settings . legend . labelColor } ; legendData . dataPoints = taskTypes . types . map ( ( typeMeta ) : LegendDataPoint => { let color = settings . taskConfig . fill ; if ( ! useDefaultColor && ! colorHelper . isHighContrast ) { color = colorHelper . getColorForMeasure ( typeMeta . columnGroup . objects , typeMeta . name ) ; } return { label : typeMeta . name , color : color , selected : false , identity : host . createSelectionIdBuilder ( ) . withCategory ( typeMeta . selectionColumn , 0 ) . createSelectionId ( ) } ; } ) ; return legendData ; } private static getSortingOptions ( dataView ) { let sortingOption = new SortingOptions ( ) ; dataView . metadata . columns . forEach ( column => { if ( column . roles && column . sort && ( column . roles [ ParentColumnName ] || column . roles [ TaskColumnName ] ) ) { sortingOption . isCustomSortingNeeded = true ; sortingOption . sortingDirection = column . sort ; return sortingOption ; } } ) ; return sortingOption ; } private static getMinDurationUnitInMilliseconds ( durationUnit ) { switch ( durationUnit ) { case "hour" : return MillisecondsInAHour ; case "minute" : return MillisecondsInAMinute ; case "second" : return MillisecondsInASecond ; default : return MillisecondsInADay ; } } private static getUniqueMilestones ( milestonesDataPoints ) { const milestonesWithoutDublicates = { } ; milestonesDataPoints . forEach ( ( milestone ) => { if ( milestone . name ) { milestonesWithoutDublicates [ milestone . name ] = milestone ; } } ) ; return milestonesWithoutDublicates ; } private static createMilestones ( dataView , host ) { let milestonesIndex = - 1 ; for ( const index in dataView . categorical . categories ) { const category = dataView . categorical . categories [ index ] ; if ( category . source . roles . Milestones ) { milestonesIndex = + index ; } } let milestoneData = { dataPoints : [ ] } ; const milestonesCategory = dataView . categorical . categories [ milestonesIndex ] ; let milestones = [ ] ; if ( milestonesCategory && milestonesCategory . values ) { milestonesCategory . values . forEach ( ( value , index ) => milestones . push ( { value , index } ) ) ; milestones . forEach ( ( milestone ) => { const milestoneObjects = milestonesCategory . objects && milestonesCategory . objects [ milestone . index ] ; const selectionBuilder = host . createSelectionIdBuilder ( ) . withCategory ( milestonesCategory , milestone . index ) ; const milestoneDataPoint = { name : milestone . value as string , identity : selectionBuilder . createSelectionId ( ) , shapeType : milestoneObjects && milestoneObjects . milestones && milestoneObjects . milestones . shapeType ? milestoneObjects . milestones . shapeType as string : MilestoneShapeTypes . Rhombus , color : milestoneObjects && milestoneObjects . milestones && milestoneObjects . milestones . fill ? ( milestoneObjects . milestones as any ) . fill . solid . color : Gantt . DefaultValues . TaskColor } ; milestoneData . dataPoints . push ( milestoneDataPoint ) ; } ) ; } return milestoneData ; } param dataView param formatters param taskColor param settings param colors param host param taskTypes private static createTasks ( dataView , taskTypes , host , formatters , colors , settings , taskColor , localizationManager , isEndDateFillled ) { let tasks = [ ] , addedParents = [ ] ; const values = GanttColumns . getCategoricalValues ( dataView ) ; if ( ! values . Task ) { return tasks ; } const colorHelper = new ColorHelper ( colors , Gantt . LegendPropertyIdentifier ) ; const groupValues = GanttColumns . getGroupedValueColumns ( dataView ) ; const sortingOptions = Gantt . getSortingOptions ( dataView ) ; let collapsedTasks = JSON . parse ( settings . collapsedTasks . list ) ; let durationUnit = settings . general . durationUnit ; let duration = settings . general . durationMin ; let taskProgressShow = settings . taskCompletion . show ; let endDate = null ; values . Task . forEach ( ( categoryValue , index ) => { let color = taskColor || Gantt . DefaultValues . TaskColor ; let completion = 0 ; let taskType = null ; let wasDowngradeDurationUnit = false ; let tooltips = [ ] ; let stepDurationTransformation = 0 ; const selectionBuilder = host . createSelectionIdBuilder ( ) . withCategory ( dataView . categorical . categories [ 0 ] , index ) ; if ( groupValues ) { groupValues . forEach ( ( group ) => { let maxCompletionFromTasks = _ . max ( values . Completion ) ; maxCompletionFromTasks = maxCompletionFromTasks > Gantt . ComplectionMax ? Gantt . ComplectionMaxInPercent : Gantt . ComplectionMax ; if ( group . Duration && group . Duration . values [ index ] !== null ) { taskType = _ . find ( taskTypes . types , ( typeMeta ) => typeMeta . name === group . Duration . source . groupName ) ; if ( taskType ) { selectionBuilder . withCategory ( taskType . selectionColumn , 0 ) ; color = colorHelper . getColorForMeasure ( taskType . columnGroup . objects , taskType . name ) ; } duration = group . Duration . values [ index ] > settings . general . durationMin ? group . Duration . values [ index ] as number : settings . general . durationMin ; if ( duration && duration % 1 !== 0 ) { durationUnit = DurationHelper . downgradeDurationUnit ( durationUnit , duration ) ; stepDurationTransformation = GanttDurationUnitType . indexOf ( settings . general . durationUnit ) - GanttDurationUnitType . indexOf ( durationUnit ) ; duration = DurationHelper . transformDuration ( duration , durationUnit , stepDurationTransformation ) ; wasDowngradeDurationUnit = true ; } completion = ( ( group . Completion && group . Completion . values [ index ] ) && taskProgressShow && Gantt . convertToDecimal ( group . Completion . values [ index ] as number , settings . taskCompletion . maxCompletion , maxCompletionFromTasks ) ) || null ; if ( completion !== null ) { if ( completion < Gantt . ComplectionMin ) { completion = Gantt . ComplectionMin ; } if ( completion > Gantt . ComplectionMax ) { completion = Gantt . ComplectionMax ; } } } else if ( group . EndDate && group . EndDate . values [ index ] !== null ) { taskType = _ . find ( taskTypes . types , ( typeMeta ) => typeMeta . name === group . EndDate . source . groupName ) ; if ( taskType ) { selectionBuilder . withCategory ( taskType . selectionColumn , 0 ) ; color = colorHelper . getColorForMeasure ( taskType . columnGroup . objects , taskType . name ) ; } endDate = group . EndDate . values [ index ] ? group . EndDate . values [ index ] as Date : null ; if ( typeof ( endDate ) === "string" || typeof ( endDate ) === "number" ) { endDate = new Date ( endDate ) ; } completion = ( ( group . Completion && group . Completion . values [ index ] ) && taskProgressShow && Gantt . convertToDecimal ( group . Completion . values [ index ] as number , settings . taskCompletion . maxCompletion , maxCompletionFromTasks ) ) || null ; if ( completion !== null ) { if ( completion < Gantt . ComplectionMin ) { completion = Gantt . ComplectionMin ; } if ( completion > Gantt . ComplectionMax ) { completion = Gantt . ComplectionMax ; } } } } ) ; } const selectionId = selectionBuilder . createSelectionId ( ) ; const extraInformation = [ ] ; const resource = ( values . Resource && values . Resource [ index ] as string ) || "" ; const parent = ( values . Parent && values . Parent [ index ] as string ) || null ; const Milestone = ( values . Milestones && ! _ . isEmpty ( values . Milestones [ index ] ) && values . Milestones [ index ] ) || null ; const startDate = ( values . StartDate && values . StartDate [ index ] && isValidDate ( new Date ( values . StartDate [ index ] ) ) && new Date ( values . StartDate [ index ] ) ) || new Date ( Date . now ( ) ) ; if ( values . ExtraInformation ) { const extraInformationKeys = Object . keys ( values . ExtraInformation ) ; for ( const key of extraInformationKeys ) { const value = values . ExtraInformation [ key ] [ index ] ; if ( value ) { extraInformation . push ( { displayName : key , value : value } ) ; } } } const task = { color , completion , resource , index : null , name : categoryValue as string , start : startDate , end : endDate , parent , children : null , visibility : true , duration , taskType : taskType && taskType . name , description : categoryValue as string , tooltipInfo : tooltips , selected : false , identity : selectionId , extraInformation , daysOffList : [ ] , wasDowngradeDurationUnit , stepDurationTransformation , Milestones : Milestone && startDate ? [ { type : Milestone , start : startDate , tooltipInfo : null , category : categoryValue as string } ] : [ ] } ; if ( parent ) { let parentTask = null ; if ( addedParents . indexOf ( parent ) === - 1 ) { addedParents . push ( parent ) ; parentTask = { index : 0 , name : parent , start : null , duration : null , completion : null , resource : null , end : null , parent : null , children : [ task ] , visibility : true , taskType : null , description : null , color : null , tooltipInfo : null , extraInformation : _ . includes ( collapsedTasks , parent ) ? extraInformation : null , daysOffList : null , wasDowngradeDurationUnit : null , selected : null , identity : selectionBuilder . createSelectionId ( ) , Milestones : Milestone && startDate ? [ { type : Milestone , start : startDate , tooltipInfo : null , category : categoryValue as string } ] : [ ] } ; tasks . push ( parentTask ) ; } else { parentTask = tasks . filter ( x => x . index === 0 && x . name === parent ) [ 0 ] ; parentTask . children . push ( task ) ; } } tasks . push ( task ) ; } ) ; Gantt . downgradeDurationUnitIfNeeded ( tasks , durationUnit ) ; if ( values . Parent ) { tasks = Gantt . sortTasksWithParents ( tasks , sortingOptions ) ; } tasks . forEach ( task => { if ( task . children && task . children . length ) { return ; } if ( task . end && task . start && isValidDate ( task . end ) ) { const durationInMilliseconds = task . end . getTime ( ) - task . start . getTime ( ) , minDurationUnitInMilliseconds = Gantt . getMinDurationUnitInMilliseconds ( durationUnit ) ; task . end = durationInMilliseconds < minDurationUnitInMilliseconds ? Gantt . getEndDate ( durationUnit , task . start , task . duration ) : task . end ; } else { task . end = isValidDate ( task . end ) ? task . end : Gantt . getEndDate ( durationUnit , task . start , task . duration ) ; } if ( settings . daysOff . show && duration ) { let datesDiff = 0 ; do { task . daysOffList = Gantt . calculateDaysOff ( + settings . daysOff . firstDayOfWeek , new Date ( task . start . getTime ( ) ) , new Date ( task . end . getTime ( ) ) ) ; if ( task . daysOffList . length ) { const isDurationFilled = _ . findIndex ( dataView . metadata . columns , col => col . roles . hasOwnProperty ( GanttRoles . Duration ) ) !== - 1 ; if ( isDurationFilled ) { let extraDuration = Gantt . calculateExtraDurationDaysOff ( task . daysOffList , task . start , task . end , + settings . daysOff . firstDayOfWeek , durationUnit ) ; task . end = Gantt . getEndDate ( durationUnit , task . start , task . duration + extraDuration ) ; } const lastDayOffListItem = task . daysOffList [ task . daysOffList . length - 1 ] ; const lastDayOff = lastDayOffListItem [ 1 ] === 1 ? lastDayOffListItem [ 0 ] : new Date ( lastDayOffListItem [ 0 ] . getFullYear ( ) , lastDayOffListItem [ 0 ] . getMonth ( ) , lastDayOffListItem [ 0 ] . getDate ( ) + 1 ) ; datesDiff = Math . ceil ( ( task . end . getTime ( ) - lastDayOff . getTime ( ) ) / MillisecondsInADay ) ; } } while ( task . daysOffList . length && datesDiff - DaysInAWeekend > DaysInAWeek ) ; } if ( task . parent ) { task . visibility = collapsedTasks . indexOf ( task . parent ) === - 1 ; } } ) ; tasks . forEach ( ( task ) => { if ( ! task . children || _ . includes ( collapsedTasks , task . name ) ) { task . tooltipInfo = Gantt . getTooltipInfo ( task , formatters , durationUnit , localizationManager , isEndDateFillled ) ; if ( task . Milestones ) { task . Milestones . forEach ( ( milestone ) => { const dateFormatted = formatters . startDateFormatter . format ( task . start ) ; const dateTypesSettings = settings . dateType ; milestone . tooltipInfo = Gantt . getTooltipForMilestoneLine ( dateFormatted , localizationManager , dateTypesSettings , [ milestone . type ] , [ milestone . category ] ) ; } ) ; } } } ) ; return tasks ; } public static sortTasksWithParents ( tasks , sortingOptions ) { const sortingFunction = ( ( a , b ) => { if ( a . name < b . name ) { return sortingOptions . sortingDirection === SortDirection . Ascending ? - 1 : 1 ; } if ( a . name > b . name ) { return sortingOptions . sortingDirection === SortDirection . Ascending ? 1 : - 1 ; } return 0 ; } ) ; if ( sortingOptions . isCustomSortingNeeded ) { tasks . sort ( sortingFunction ) ; } let index = 0 ; tasks . forEach ( task => { if ( ! task . index && ! task . parent ) { task . index = index ++ ; if ( task . children ) { if ( sortingOptions . isCustomSortingNeeded ) { task . children . sort ( sortingFunction ) ; } task . children . forEach ( subtask => { subtask . index = subtask . index === null ? index ++ : subtask . index ; } ) ; } } } ) ; let resultTasks = [ ] ; tasks . forEach ( ( task ) => { resultTasks [ task . index ] = task ; } ) ; return resultTasks ; } param daysOffDataForAddition param firstDayOfWeek param date param extraCondition private static addNextDaysOff ( daysOffDataForAddition , firstDayOfWeek , date , extraCondition = false ) { daysOffDataForAddition . amountOfLastDaysOff = 1 ; for ( let i = DaysInAWeekend ; i > 0 ; i -- ) { let dateForCheck = new Date ( date . getTime ( ) + ( i * MillisecondsInADay ) ) ; let alreadyInDaysOffList = false ; daysOffDataForAddition . list . forEach ( ( item ) => { const itemDate = item [ 0 ] ; if ( itemDate . getFullYear ( ) === date . getFullYear ( ) && itemDate . getMonth ( ) === date . getMonth ( ) && itemDate . getDate ( ) === date . getDate ( ) ) { alreadyInDaysOffList = true ; } } ) ; const isFirstDaysOfWeek = dateForCheck . getDay ( ) === + firstDayOfWeek ; const isFirstDayOff = dateForCheck . getDay ( ) === ( + firstDayOfWeek + 5 ) % 7 ; const isSecondDayOff = dateForCheck . getDay ( ) === ( + firstDayOfWeek + 6 ) % 7 ; const isPartlyUsed = ! /00\:00\:00/g . test ( dateForCheck . toTimeString ( ) ) ; if ( ! alreadyInDaysOffList && isFirstDaysOfWeek && ( ! extraCondition || ( extraCondition && isPartlyUsed ) ) ) { daysOffDataForAddition . amountOfLastDaysOff = i ; daysOffDataForAddition . list . push ( [ new Date ( date . getFullYear ( ) , date . getMonth ( ) , date . getDate ( ) , 0 , 0 , 0 ) , i ] ) ; } if ( ! alreadyInDaysOffList && ( isFirstDayOff || isSecondDayOff ) && isPartlyUsed ) { const amount = isFirstDayOff ? 2 : 1 ; daysOffDataForAddition . amountOfLastDaysOff = amount ; daysOffDataForAddition . list . push ( [ new Date ( dateForCheck . getFullYear ( ) , dateForCheck . getMonth ( ) , dateForCheck . getDate ( ) , 0 , 0 , 0 ) , amount ] ) ; } } return daysOffDataForAddition ; } param durationUnit param start param step public static getEndDate ( durationUnit , start , step ) { switch ( durationUnit ) { case DurationUnits . Second . toString ( ) : return d3 . timeSecond . offset ( start , step ) ; case DurationUnits . Minute . toString ( ) : return d3 . timeMinute . offset ( start , step ) ; case DurationUnits . Hour . toString ( ) : return d3 . timeHour . offset ( start , step ) ; default : return d3 . timeDay . offset ( start , step ) ; } } private static isDayOff ( date , firstDayOfWeek ) { const isFirstDayOff = date . getDay ( ) === ( + firstDayOfWeek + 5 ) % 7 ; const isSecondDayOff = date . getDay ( ) === ( + firstDayOfWeek + 6 ) % 7 ; return isFirstDayOff || isSecondDayOff ; } private static isOneDay ( firstDate , secondDate ) { return firstDate . getMonth ( ) === secondDate . getMonth ( ) && firstDate . getFullYear ( ) === secondDate . getFullYear ( ) && firstDate . getDay ( ) === secondDate . getDay ( ) ; } param firstDayOfWeek param fromDate param toDate private static calculateDaysOff ( firstDayOfWeek , fromDate , toDate ) { let tempDaysOffData = { list : [ ] , amountOfLastDaysOff : 0 } ; if ( Gantt . isOneDay ( fromDate , toDate ) ) { if ( ! Gantt . isDayOff ( fromDate , + firstDayOfWeek ) ) { return tempDaysOffData . list ; } } while ( fromDate < toDate ) { Gantt . addNextDaysOff ( tempDaysOffData , firstDayOfWeek , fromDate ) ; fromDate . setDate ( fromDate . getDate ( ) + tempDaysOffData . amountOfLastDaysOff ) ; } Gantt . addNextDaysOff ( tempDaysOffData , firstDayOfWeek , toDate , true ) ; return tempDaysOffData . list ; } private static convertMillisecondsToDuration ( milliseconds , durationUnit ) { switch ( durationUnit ) { case DurationUnits . Hour . toString ( ) : return milliseconds /= MillisecondsInAHour ; case DurationUnits . Minute . toString ( ) : return milliseconds /= MillisecondsInAMinute ; case DurationUnits . Second . toString ( ) : return milliseconds /= MillisecondsInASecond ; default : return milliseconds /= MillisecondsInADay ; } } private static calculateExtraDurationDaysOff ( daysOffList , startDate , endDate , firstDayOfWeek , durationUnit ) { let extraDuration = 0 ; for ( let i = 0 ; i < daysOffList . length ; i ++ ) { const itemAmount = daysOffList [ i ] [ 1 ] ; extraDuration += itemAmount ; if ( itemAmount === 2 && ( i + 1 ) < daysOffList . length ) { const itemDate = daysOffList [ i ] [ 0 ] . getDate ( ) ; const nextDate = daysOffList [ i + 1 ] [ 0 ] . getDate ( ) ; if ( itemDate + 1 === nextDate ) { i += 2 ; } } } if ( this . isDayOff ( startDate , firstDayOfWeek ) ) { let prevDayTimestamp = startDate . getTime ( ) ; let prevDate = new Date ( prevDayTimestamp ) ; prevDate . setHours ( 0 , 0 , 0 ) ; let alreadyAccountedDuration = startDate . getTime ( ) - prevDate . getTime ( ) ; alreadyAccountedDuration = Gantt . convertMillisecondsToDuration ( alreadyAccountedDuration , durationUnit ) ; extraDuration = DurationHelper . transformExtraDuration ( durationUnit , extraDuration ) ; extraDuration -= alreadyAccountedDuration ; } return extraDuration ; } param dataView param host param colors public static converter ( dataView , host , colors , colorHelper , localizationManager ) { if ( ! dataView || ! dataView . categorical || ! Gantt . isChartHasTask ( dataView ) || dataView . categorical . categories . length === 0 ) { return null ; } const settings = this . parseSettings ( dataView , colorHelper ) ; const taskTypes = Gantt . getAllTasksTypes ( dataView ) ; const formatters = this . getFormatters ( dataView , settings , host . locale || null ) ; const isDurationFilled = _ . findIndex ( dataView . metadata . columns , col => col . roles . hasOwnProperty ( GanttRoles . Duration ) ) !== - 1 , isEndDateFillled = _ . findIndex ( dataView . metadata . columns , col => col . roles . hasOwnProperty ( GanttRoles . EndDate ) ) !== - 1 , isParentFilled = _ . findIndex ( dataView . metadata . columns , col => col . roles . hasOwnProperty ( GanttRoles . Parent ) ) !== - 1 , isResourcesFilled = _ . findIndex ( dataView . metadata . columns , col => col . roles . hasOwnProperty ( GanttRoles . Resource ) ) !== - 1 ; const legendData = Gantt . createLegend ( host , colors , settings , taskTypes , ! isDurationFilled && ! isEndDateFillled ) ; const milestonesData = Gantt . createMilestones ( dataView , host ) ; let taskColor = ( legendData . dataPoints . length <= 1 ) || ! isDurationFilled ? settings . taskConfig . fill : null ; const tasks = Gantt . createTasks ( dataView , taskTypes , host , formatters , colors , settings , taskColor , localizationManager , isEndDateFillled ) ; const types = _ . groupBy ( tasks , x => x . taskType ) ; legendData . dataPoints = legendData . dataPoints . filter ( x => types [ x . label ] ) ; return { dataView , settings , taskTypes , tasks , legendData , milestonesData , isDurationFilled , isEndDateFillled , isParentFilled , isResourcesFilled } ; } public static parseSettings ( dataView , colorHelper ) { let settings = GanttSettings . parse < GanttSettings > ( dataView ) ; if ( ! colorHelper ) { return settings ; } if ( settings . taskCompletion . maxCompletion < Gantt . ComplectionMin || settings . taskCompletion . maxCompletion > Gantt . ComplectionMaxInPercent ) { settings . taskCompletion . maxCompletion = Gantt . ComplectionDefault ; } if ( colorHelper . isHighContrast ) { settings . dateType . axisColor = colorHelper . getHighContrastColor ( "foreground" , settings . dateType . axisColor ) ; settings . dateType . axisTextColor = colorHelper . getHighContrastColor ( "foreground" , settings . dateType . axisTextColor ) ; settings . dateType . todayColor = colorHelper . getHighContrastColor ( "foreground" , settings . dateType . todayColor ) ; settings . daysOff . fill = colorHelper . getHighContrastColor ( "foreground" , settings . daysOff . fill ) ; settings . taskConfig . fill = colorHelper . getHighContrastColor ( "foreground" , settings . taskConfig . fill ) ; settings . taskLabels . fill = colorHelper . getHighContrastColor ( "foreground" , settings . taskLabels . fill ) ; settings . taskResource . fill = colorHelper . getHighContrastColor ( "foreground" , settings . taskResource . fill ) ; settings . legend . labelColor = colorHelper . getHighContrastColor ( "foreground" , settings . legend . labelColor ) ; } return settings ; } private static convertToDecimal ( value , maxCompletionFromSettings , maxCompletionFromTasks ) { if ( maxCompletionFromSettings ) { return value / maxCompletionFromSettings ; } return value / maxCompletionFromTasks ; } param dataView private static getAllTasksTypes ( dataView ) { const taskTypes = { typeName : "" , types : [ ] } ; let index = _ . findIndex ( dataView . metadata . columns , col => col . roles . hasOwnProperty ( GanttRoles . Legend ) ) ; if ( index !== - 1 ) { taskTypes . typeName = dataView . metadata . columns [ index ] . displayName ; let legendMetaCategoryColumn = dataView . metadata . columns [ index ] ; let values = dataView . categorical && dataView . categorical . values || < DataViewValueColumns > [ ] ; let groupValues = values . grouped ( ) ; taskTypes . types = groupValues . map ( ( group ) : TaskTypeMetadata => { let column = { identity : [ group . identity ] , source : { displayName : null , queryName : legendMetaCategoryColumn . queryName } , values : null } ; return { name : group . name as string , selectionColumn : column , columnGroup : group } ; } ) ; } return taskTypes ; } /**
     * Get legend data, calculate position and draw it
     */ private renderLegend ( ) { if ( ! this . viewModel . legendData ) { return ; } let position = this . viewModel . settings . legend . show ? LegendPosition [ this . viewModel . settings . legend . position ] : LegendPosition . None ; this . legend . changeOrientation ( position ) ; this . legend . drawLegend ( this . viewModel . legendData , _ . clone ( this . viewport ) ) ; LegendModule . positionChartArea ( this . ganttDiv , this . legend ) ; switch ( this . legend . getOrientation ( ) ) { case LegendPosition . Left : case LegendPosition . LeftCenter : case LegendPosition . Right : case LegendPosition . RightCenter : this . viewport . width -= this . legend . getMargins ( ) . width ; break ; case LegendPosition . Top : case LegendPosition . TopCenter : case LegendPosition . Bottom : case LegendPosition . BottomCenter : this . viewport . height -= this . legend . getMargins ( ) . height ; break ; } } private scaleAxisLength ( axisLength ) { let fullScreenAxisLength = Gantt . DefaultGraphicWidthPercentage * this . viewport . width ; if ( axisLength < fullScreenAxisLength ) { axisLength = fullScreenAxisLength ; } return axisLength ; } param options public update ( options ) { if ( ! options || ! options . dataViews || ! options . dataViews [ 0 ] ) { this . clearViewport ( ) ; return ; } this . viewModel = Gantt . converter ( options . dataViews [ 0 ] , this . host , this . colors , this . colorHelper , this . localizationManager ) ; if ( this . viewModel && this . viewModel . milestonesData ) { let newMilestoneData = this . viewModel . milestonesData ; const milestonesWithoutDublicates = Gantt . getUniqueMilestones ( newMilestoneData . dataPoints ) ; newMilestoneData . dataPoints . forEach ( ( dataPoint ) => { if ( dataPoint . name ) { const theSameUniqDataPoint = milestonesWithoutDublicates [ dataPoint . name ] ; dataPoint . color = theSameUniqDataPoint . color ; dataPoint . shapeType = theSameUniqDataPoint . shapeType ; } } ) ; this . viewModel . milestonesData = newMilestoneData ; } if ( ! this . viewModel || ! this . viewModel . tasks || this . viewModel . tasks . length <= 0 ) { this . clearViewport ( ) ; return ; } this . viewport = _ . clone ( options . viewport ) ; this . margin = Gantt . DefaultMargin ; this . eventService . renderingStarted ( options ) ; this . renderLegend ( ) ; this . updateChartSize ( ) ; const visibleTasks = this . viewModel . tasks . filter ( ( task ) => task . visibility ) ; const tasks = visibleTasks . map ( ( task , i ) => { task . index = i ; return task ; } ) ; if ( this . interactivityService ) { this . interactivityService . applySelectionStateToData ( tasks ) ; } if ( tasks . length < Gantt . MinTasks ) { return ; } let settings = this . viewModel . settings ; this . collapsedTasks = JSON . parse ( settings . collapsedTasks . list ) ; let groupedTasks = this . groupTasks ( tasks ) ; this . updateCommonTasks ( groupedTasks ) ; this . updateCommonMilestones ( groupedTasks ) ; let tasksAfterGrouping = [ ] ; groupedTasks . forEach ( ( t ) => tasksAfterGrouping = tasksAfterGrouping . concat ( t . tasks ) ) ; const minDateTask = _ . minBy ( tasksAfterGrouping , ( t ) => t && t . start ) ; const maxDateTask = _ . maxBy ( tasksAfterGrouping , ( t ) => t && t . end ) ; this . hasNotNullableDates = ! ! minDateTask && ! ! maxDateTask ; let axisLength = 0 ; if ( this . hasNotNullableDates ) { let startDate = minDateTask . start ; let endDate = maxDateTask . end ; if ( startDate . toString ( ) === endDate . toString ( ) ) { endDate = new Date ( endDate . valueOf ( ) + ( 24 * 60 * 60 * 1000 ) ) ; } let dateTypeMilliseconds = Gantt . getDateType ( settings . dateType . type ) ; let ticks = Math . ceil ( Math . round ( endDate . valueOf ( ) - startDate . valueOf ( ) ) / dateTypeMilliseconds ) ; ticks = ticks < 2 ? 2 : ticks ; axisLength = ticks * Gantt . DefaultTicksLength ; axisLength = this . scaleAxisLength ( axisLength ) ; let viewportIn = { height : this . viewport . height , width : axisLength } ; let xAxisProperties = this . calculateAxes ( viewportIn , this . textProperties , startDate , endDate , ticks , false ) ; this . timeScale = < timeScale < Date , Date > > xAxisProperties . scale ; this . renderAxis ( xAxisProperties ) ; } axisLength = this . scaleAxisLength ( axisLength ) ; this . setDimension ( groupedTasks , axisLength , settings ) ; this . renderTasks ( groupedTasks ) ; this . updateTaskLabels ( groupedTasks , settings . taskLabels . width ) ; this . updateElementsPositions ( this . margin ) ; this . createMilestoneLine ( groupedTasks ) ; if ( settings . general . scrollToCurrentTime && this . hasNotNullableDates ) { this . scrollToMilestoneLine ( axisLength ) ; } if ( this . interactivityService ) { const behaviorOptions = { clearCatcher : this . clearCatcher , taskSelection : this . taskGroup . selectAll ( Selectors . SingleTask . selectorName ) , legendSelection : this . body . selectAll ( Selectors . LegendItems . selectorName ) , subTasksCollapse : { selection : this . body . selectAll ( Selectors . ClickableArea . selectorName ) , callback : this . subTasksCollapseCb . bind ( this ) } , allSubtasksCollapse : { selection : this . body . selectAll ( Selectors . CollapseAllArrow . selectorName ) , callback : this . subTasksCollapseAll . bind ( this ) } , interactivityService : this . interactivityService , behavior : this . behavior , dataPoints : tasks } ; this . interactivityService . bind ( behaviorOptions ) ; this . behavior . renderSelection ( false ) ; } this . eventService . renderingFinished ( options ) ; } private static getDateType ( dateType ) { switch ( dateType ) { case DateTypes . Second : return MillisecondsInASecond ; case DateTypes . Minute : return MillisecondsInAMinute ; case DateTypes . Hour : return MillisecondsInAHour ; case DateTypes . Day : return MillisecondsInADay ; case DateTypes . Week : return MillisecondsInWeek ; case DateTypes . Month : return MillisecondsInAMonth ; case DateTypes . Quarter : return MillisecondsInAQuarter ; case DateTypes . Year : return MillisecondsInAYear ; default : return MillisecondsInWeek ; } } private calculateAxes ( viewportIn , textProperties , startDate , endDate , ticksCount , scrollbarVisible ) { let dataTypeDatetime = ValueType . fromPrimitiveTypeAndCategory ( PrimitiveType . Date ) ; let category = { displayName : this . localizationManager . getDisplayName ( "Role_StartDate" ) , queryName : GanttRoles . StartDate , type : dataTypeDatetime , index : 0 } ; let visualOptions = { viewport : viewportIn , margin : this . margin , forcedXDomain : [ startDate , endDate ] , forceMerge : false , showCategoryAxisLabel : false , showValueAxisLabel : false , categoryAxisScaleType : axisScale . linear , valueAxisScaleType : null , valueAxisDisplayUnits : 0 , categoryAxisDisplayUnits : 0 , trimOrdinalDataOnOverflow : false , forcedTickCount : ticksCount } ; const width = viewportIn . width ; let axes = this . calculateAxesProperties ( viewportIn , visualOptions , category ) ; axes . willLabelsFit = AxisHelper . LabelLayoutStrategy . willLabelsFit ( axes , width , textMeasurementService . measureSvgTextWidth , textProperties ) ; axes . willLabelsWordBreak = ( ! axes . willLabelsFit && ! scrollbarVisible ) && AxisHelper . LabelLayoutStrategy . willLabelsWordBreak ( axes , this . margin , width , textMeasurementService . measureSvgTextWidth , textMeasurementService . estimateSvgTextHeight , textMeasurementService . getTailoredTextOrDefault , textProperties ) ; return axes ; } private calculateAxesProperties ( viewportIn , options , metaDataColumn ) { const dateType = this . viewModel . settings . dateType . type ; const cultureSelector = this . host . locale ; let xAxisDateFormatter = ValueFormatter . create ( { format : Gantt . DefaultValues . DateFormatStrings [ dateType ] , cultureSelector } ) ; let xAxisProperties = AxisHelper . createAxis ( { pixelSpan : viewportIn . width , dataDomain : options . forcedXDomain , metaDataColumn : metaDataColumn , formatString : Gantt . DefaultValues . DateFormatStrings [ dateType ] , outerPadding : 0 , isScalar : true , isVertical : false , forcedTickCount : options . forcedTickCount , useTickIntervalForDisplayUnits : true , isCategoryAxis : true , getValueFn : ( index ) => { return xAxisDateFormatter . format ( new Date ( index ) ) ; } , scaleType : options . categoryAxisScaleType , axisDisplayUnits : options . categoryAxisDisplayUnits , } ) ; xAxisProperties . axisLabel = metaDataColumn . displayName ; return xAxisProperties ; } private setDimension ( groupedTasks , axisLength , settings ) { const fullResourceLabelMargin = groupedTasks . length * this . getResourceLabelTopMargin ( ) ; let widthBeforeConvertion = this . margin . left + settings . taskLabels . width + axisLength ; if ( settings . taskResource . show && settings . taskResource . position === ResourceLabelPositions . Right ) { widthBeforeConvertion += Gantt . DefaultValues . ResourceWidth ; } else { widthBeforeConvertion += Gantt . DefaultValues . ResourceWidth / 2 ; } const height = PixelConverter . toString ( groupedTasks . length * ( settings . taskConfig . height || DefaultChartLineHeight ) + this . margin . top + fullResourceLabelMargin ) ; const width = PixelConverter . toString ( widthBeforeConvertion ) ; this . ganttSvg . attr ( "height" , height ) . attr ( "width" , width ) ; } private groupTasks ( tasks ) { if ( this . viewModel . settings . general . groupTasks ) { let groupedTasks = _ . groupBy ( tasks , x => ( x . parent ? `${ x . parent }.${ x . name }` : x . name ) ) ; let result = [ ] ; const taskKeys = Object . keys ( groupedTasks ) ; let alreadyReviewedKeys = [ ] ; taskKeys . forEach ( ( key ) => { const isKeyAlreadyReviewed = _ . includes ( alreadyReviewedKeys , key ) ; if ( ! isKeyAlreadyReviewed ) { let name = key ; if ( groupedTasks [ key ] && groupedTasks [ key ] . length && groupedTasks [ key ] [ 0 ] . parent && key . indexOf ( groupedTasks [ key ] [ 0 ] . parent ) !== - 1 ) { name = key . substr ( groupedTasks [ key ] [ 0 ] . parent . length + 1 , key . length ) ; } const taskRecord = < GroupedTask > { name , tasks : groupedTasks [ key ] } ; result . push ( taskRecord ) ; alreadyReviewedKeys . push ( key ) ; groupedTasks [ key ] . forEach ( ( task ) => { if ( task . children && ! _ . includes ( this . collapsedTasks , task . name ) ) { task . children . forEach ( ( childrenTask ) => { const childrenFullName = `${ name }.${ childrenTask . name }` ; const isChildrenKeyAlreadyReviewed = _ . includes ( alreadyReviewedKeys , childrenFullName ) ; if ( ! isChildrenKeyAlreadyReviewed ) { const childrenRecord = < GroupedTask > { name : childrenTask . name , tasks : groupedTasks [ childrenFullName ] } ; result . push ( childrenRecord ) ; alreadyReviewedKeys . push ( childrenFullName ) ; } } ) ; } } ) ; } } ) ; result . forEach ( ( x , i ) => { x . tasks . forEach ( t => t . index = i ) ; x . index = i ; } ) ; return result ; } return tasks . map ( x => < GroupedTask > { name : x . name , index : x . index , tasks : [ x ] } ) ; } private renderAxis ( xAxisProperties , duration = Gantt . DefaultDuration ) { const axisColor = this . viewModel . settings . dateType . axisColor ; const axisTextColor = this . viewModel . settings . dateType . axisTextColor ; let xAxis = xAxisProperties . axis ; this . axisGroup . call ( xAxis . tickSizeOuter ( xAxisProperties . outerPadding ) ) ; this . axisGroup . transition ( ) . duration ( duration ) . call ( xAxis ) ; this . axisGroup . selectAll ( "path" ) . style ( "stroke" , axisColor ) ; this . axisGroup . selectAll ( ".tick line" ) . style ( "stroke" , ( timestamp ) => this . setTickColor ( timestamp , axisColor ) ) ; this . axisGroup . selectAll ( ".tick text" ) . style ( "fill" , ( timestamp ) => this . setTickColor ( timestamp , axisTextColor ) ) ; } private setTickColor ( timestamp , defaultColor ) { const tickTime = new Date ( timestamp ) ; const firstDayOfWeek = this . viewModel . settings . daysOff . firstDayOfWeek ; const color = this . viewModel . settings . daysOff . fill ; if ( this . viewModel . settings . daysOff . show ) { let dateForCheck = new Date ( tickTime . getTime ( ) ) ; for ( let i = 0 ; i <= DaysInAWeekend ; i ++ ) { if ( dateForCheck . getDay ( ) === + firstDayOfWeek ) { return ! i ? defaultColor : color ; } dateForCheck . setDate ( dateForCheck . getDate ( ) + 1 ) ; } } return defaultColor ; } param tasks param width private updateTaskLabels ( tasks , width ) { let axisLabel ; let taskLabelsShow = this . viewModel . settings . taskLabels . show ; let displayGridLines = this . viewModel . settings . general . displayGridLines ; let taskLabelsColor = this . viewModel . settings . taskLabels . fill ; let taskLabelsFontSize = this . viewModel . settings . taskLabels . fontSize ; let taskLabelsWidth = this . viewModel . settings . taskLabels . width ; let taskConfigHeight = this . viewModel . settings . taskConfig . height || DefaultChartLineHeight ; const categoriesAreaBackgroundColor = this . colorHelper . getThemeColor ( ) ; const isHighContrast = this . colorHelper . isHighContrast ; if ( taskLabelsShow ) { this . lineGroupWrapper . attr ( "width" , taskLabelsWidth ) . attr ( "fill" , isHighContrast ? categoriesAreaBackgroundColor : Gantt . DefaultValues . TaskCategoryLabelsRectColor ) . attr ( "stroke" , this . colorHelper . getHighContrastColor ( "foreground" , Gantt . DefaultValues . TaskLineColor ) ) . attr ( "stroke-width" , 1 ) ; this . lineGroup . selectAll ( Selectors . Label . selectorName ) . remove ( ) ; axisLabel = this . lineGroup . selectAll ( Selectors . Label . selectorName ) . data ( tasks ) ; let axisLabelGroup = axisLabel . enter ( ) . append ( "g" ) . merge ( axisLabel ) ; axisLabelGroup . classed ( Selectors . Label . className , true ) . attr ( "transform" , ( task ) => SVGManipulations . translate ( 0 , this . margin . top + this . getTaskLabelCoordinateY ( task . index ) ) ) ; const clickableArea = axisLabelGroup . append ( "g" ) . classed ( Selectors . ClickableArea . className , true ) . merge ( axisLabelGroup ) ; clickableArea . append ( "text" ) . attr ( "x" , ( task ) => ( Gantt . TaskLineCoordinateX + ( _ . every ( task . tasks , ( task ) => ! ! task . parent ) ? Gantt . SubtasksLeftMargin : ( task . tasks [ 0 ] . children && ! ! task . tasks [ 0 ] . children . length ) ? this . parentLabelOffset : 0 ) ) ) . attr ( "class" , ( task ) => task . tasks [ 0 ] . children ? "parent" : task . tasks [ 0 ] . parent ? "child" : "normal-node" ) . attr ( "y" , ( task ) => ( task . index + 0.5 ) * this . getResourceLabelTopMargin ( ) ) . attr ( "fill" , taskLabelsColor ) . attr ( "stroke-width" , Gantt . AxisLabelStrokeWidth ) . style ( "font-size" , PixelConverter . fromPoint ( taskLabelsFontSize ) ) . text ( ( task ) => task . name ) . call ( AxisHelper . LabelLayoutStrategy . clip , width - Gantt . AxisLabelClip , textMeasurementService . svgEllipsis ) . append ( "title" ) . text ( ( task ) => task . name ) ; const buttonSelection = clickableArea . filter ( ( task ) => task . tasks [ 0 ] . children && ! ! task . tasks [ 0 ] . children . length ) . append ( "svg" ) . attr ( "viewBox" , "0 0 32 32" ) . attr ( "width" , Gantt . DefaultValues . IconWidth ) . attr ( "height" , Gantt . DefaultValues . IconHeight ) . attr ( "y" , ( task ) => ( task . index + 0.5 ) * this . getResourceLabelTopMargin ( ) - Gantt . DefaultValues . IconMargin ) . attr ( "x" , Gantt . DefaultValues . BarMargin ) ; clickableArea . append ( "rect" ) . attr ( "width" , 2 * Gantt . DefaultValues . IconWidth ) . attr ( "height" , 2 * Gantt . DefaultValues . IconWidth ) . attr ( "y" , ( task ) => ( task . index + 0.5 ) * this . getResourceLabelTopMargin ( ) - Gantt . DefaultValues . IconMargin ) . attr ( "x" , Gantt . DefaultValues . BarMargin ) . attr ( "fill" , "transparent" ) ; const buttonPlusMinusColor = this . colorHelper . getHighContrastColor ( "foreground" , Gantt . DefaultValues . PlusMinusColor ) ; buttonSelection . each ( function ( task ) { let element = d3 . select ( this ) ; if ( ! task . tasks [ 0 ] . children [ 0 ] . visibility ) { drawPlusButton ( element , buttonPlusMinusColor ) ; } else { drawMinusButton ( element , buttonPlusMinusColor ) ; } } ) ; let parentTask = "" ; let childrenCount = 0 ; let currentChildrenIndex = 0 ; axisLabelGroup . append ( "rect" ) . attr ( "x" , ( task ) => { const isGrouped = this . viewModel . settings . general . groupTasks ; const drawStandartMargin = ! task . tasks [ 0 ] . parent || task . tasks [ 0 ] . parent && task . tasks [ 0 ] . parent !== parentTask ; parentTask = task . tasks [ 0 ] . parent ? task . tasks [ 0 ] . parent : task . tasks [ 0 ] . name ; if ( task . tasks [ 0 ] . children ) { parentTask = task . tasks [ 0 ] . name ; childrenCount = isGrouped ? _ . uniqBy ( task . tasks [ 0 ] . children , "name" ) . length : task . tasks [ 0 ] . children . length ; currentChildrenIndex = 0 ; } if ( task . tasks [ 0 ] . parent === parentTask ) { currentChildrenIndex ++ ; } const isLastChild = childrenCount && childrenCount === currentChildrenIndex ; return drawStandartMargin || isLastChild ? Gantt . DefaultValues . ParentTaskLeftMargin : Gantt . DefaultValues . ChildTaskLeftMargin ; } ) . attr ( "y" , ( task ) => ( task . index + 1 ) * this . getResourceLabelTopMargin ( ) + ( taskConfigHeight - this . viewModel . settings . taskLabels . fontSize ) / 2 ) . attr ( "width" , ( ) => displayGridLines ? this . viewport . width : 0 ) . attr ( "height" , 1 ) . attr ( "fill" , this . colorHelper . getHighContrastColor ( "foreground" , Gantt . DefaultValues . TaskLineColor ) ) ; axisLabel . exit ( ) . remove ( ) ; this . collapseAllGroup . selectAll ( "svg" ) . remove ( ) ; this . collapseAllGroup . selectAll ( "rect" ) . remove ( ) ; this . collapseAllGroup . selectAll ( "text" ) . remove ( ) ; if ( this . viewModel . isParentFilled ) { let categoryLabelsWidth = this . viewModel . settings . taskLabels . width ; this . collapseAllGroup . append ( "rect" ) . attr ( "width" , categoryLabelsWidth ) . attr ( "height" , 2 * Gantt . TaskLabelsMarginTop ) . attr ( "fill" , categoriesAreaBackgroundColor ) ; const expandCollapseButton = this . collapseAllGroup . append ( "svg" ) . classed ( Selectors . CollapseAllArrow . className , true ) . attr ( "viewBox" , "0 0 48 48" ) . attr ( "width" , this . groupLabelSize ) . attr ( "height" , this . groupLabelSize ) . attr ( "x" , 0 ) . attr ( "y" , this . secondExpandAllIconOffset ) . attr ( this . collapseAllFlag , ( this . collapsedTasks . length ? "1" : "0" ) ) ; expandCollapseButton . append ( "rect" ) . attr ( "width" , this . groupLabelSize ) . attr ( "height" , this . groupLabelSize ) . attr ( "x" , 0 ) . attr ( "y" , this . secondExpandAllIconOffset ) . attr ( "fill" , "transparent" ) ; const buttonExpandCollapseColor = this . colorHelper . getHighContrastColor ( "foreground" , Gantt . DefaultValues . CollapseAllColor ) ; if ( this . collapsedTasks . length ) { drawExpandButton ( expandCollapseButton , buttonExpandCollapseColor ) ; } else { drawCollapseButton ( expandCollapseButton , buttonExpandCollapseColor ) ; } this . collapseAllGroup . append ( "text" ) . attr ( "x" , this . secondExpandAllIconOffset + this . groupLabelSize ) . attr ( "y" , this . groupLabelSize ) . attr ( "font-size" , "12px" ) . attr ( "fill" , this . colorHelper . getHighContrastColor ( "foreground" , Gantt . DefaultValues . CollapseAllTextColor ) ) . text ( this . collapsedTasks . length ? "Expand All" : "Collapse All" ) ; } } else { this . lineGroupWrapper . attr ( "width" , 0 ) . attr ( "fill" , "transparent" ) ; this . collapseAllGroup . selectAll ( "image" ) . remove ( ) ; this . collapseAllGroup . selectAll ( "rect" ) . remove ( ) ; this . collapseAllGroup . selectAll ( "text" ) . remove ( ) ; this . lineGroup . selectAll ( Selectors . Label . selectorName ) . remove ( ) ; } } param taskClicked private subTasksCollapseCb ( taskClicked ) { const taskIsChild = taskClicked . tasks [ 0 ] . parent && ! taskClicked . tasks [ 0 ] . children ; const taskWithoutParentAndChildren = ! taskClicked . tasks [ 0 ] . parent && ! taskClicked . tasks [ 0 ] . children ; if ( taskIsChild || taskWithoutParentAndChildren ) { return ; } const taskClickedParent = taskClicked . tasks [ 0 ] . parent || taskClicked . tasks [ 0 ] . name ; this . viewModel . tasks . forEach ( ( task ) => { if ( task . parent === taskClickedParent && task . parent . length >= taskClickedParent . length ) { const index = this . collapsedTasks . indexOf ( task . parent ) ; if ( task . visibility ) { this . collapsedTasks . push ( task . parent ) ; } else { if ( taskClickedParent === task . parent ) { this . collapsedTasks . splice ( index , 1 ) ; } } } } ) ; this . setJsonFiltersValues ( this . collapsedTasks ) ; } /**
     * callback for subtasks collapse all click event
     */ private subTasksCollapseAll ( ) { const collapsedAllSelector = this . collapseAllGroup . select ( Selectors . CollapseAllArrow . selectorName ) ; const isCollapsed = collapsedAllSelector . attr ( this . collapseAllFlag ) ; const buttonExpandCollapseColor = this . colorHelper . getHighContrastColor ( "foreground" , Gantt . DefaultValues . CollapseAllColor ) ; collapsedAllSelector . selectAll ( "path" ) . remove ( ) ; if ( isCollapsed === "1" ) { this . collapsedTasks = [ ] ; collapsedAllSelector . attr ( this . collapseAllFlag , "0" ) ; drawCollapseButton ( collapsedAllSelector , buttonExpandCollapseColor ) ; } else { collapsedAllSelector . attr ( this . collapseAllFlag , "1" ) ; drawExpandButton ( collapsedAllSelector , buttonExpandCollapseColor ) ; this . viewModel . tasks . forEach ( ( task ) => { if ( task . parent ) { if ( task . visibility ) { this . collapsedTasks . push ( task . parent ) ; } } } ) ; } this . setJsonFiltersValues ( this . collapsedTasks ) ; } private setJsonFiltersValues ( collapsedValues ) { this . host . persistProperties ( < VisualObjectInstancesToPersist > { merge : [ { objectName : "collapsedTasks" , selector : null , properties : { list : JSON . stringify ( this . collapsedTasks ) } } ] } ) ; } param groupedTasks private renderTasks ( groupedTasks ) { let taskConfigHeight = this . viewModel . settings . taskConfig . height || DefaultChartLineHeight ; let taskGroupSelection = this . taskGroup . selectAll ( Selectors . TaskGroup . selectorName ) . data ( groupedTasks ) ; taskGroupSelection . exit ( ) . remove ( ) ; const taskGroupSelectionMerged = taskGroupSelection . enter ( ) . append ( "g" ) . merge ( taskGroupSelection ) ; taskGroupSelectionMerged . classed ( Selectors . TaskGroup . className , true ) ; let taskSelection = this . taskSelectionRectRender ( taskGroupSelectionMerged ) ; this . taskMainRectRender ( taskSelection , taskConfigHeight ) ; this . MilestonesRender ( taskSelection , taskConfigHeight ) ; this . taskProgressRender ( taskSelection ) ; this . taskDaysOffRender ( taskSelection , taskConfigHeight ) ; this . taskResourceRender ( taskSelection , taskConfigHeight ) ; this . renderTooltip ( taskSelection ) ; } param groupedTasks private updateCommonTasks ( groupedTasks ) { if ( ! this . viewModel . settings . general . groupTasks ) { groupedTasks . forEach ( ( groupedTask ) => { const currentTaskName = groupedTask . name ; if ( _ . includes ( this . collapsedTasks , currentTaskName ) ) { const firstTask = groupedTask . tasks && groupedTask . tasks [ 0 ] ; const tasks = groupedTask . tasks ; tasks . forEach ( ( task ) => { if ( task . children ) { const childrenColors = task . children . map ( ( child ) => child . color ) . filter ( ( color ) => color ) ; const minChildDateStart = _ . min ( task . children . map ( ( child ) => child . start ) . filter ( ( dateStart ) => dateStart ) ) ; const maxChildDateEnd = _ . max ( task . children . map ( ( child ) => child . end ) . filter ( ( dateStart ) => dateStart ) ) ; firstTask . color = ! firstTask . color && task . children ? childrenColors [ 0 ] : firstTask . color ; firstTask . start = _ . min ( [ firstTask . start , minChildDateStart ] ) ; firstTask . end = < any > _ . max ( [ firstTask . end , maxChildDateEnd ] ) ; } } ) ; groupedTask . tasks = firstTask && [ firstTask ] || [ ] ; } } ) ; } } param groupedTasks private updateCommonMilestones ( groupedTasks ) { groupedTasks . forEach ( ( groupedTask ) => { const currentTaskName = groupedTask . name ; if ( _ . includes ( this . collapsedTasks , currentTaskName ) ) { const lastTask = groupedTask . tasks && groupedTask . tasks [ groupedTask . tasks . length - 1 ] ; const tasks = groupedTask . tasks ; tasks . forEach ( ( task ) => { if ( task . children ) { task . children . map ( ( child ) => { if ( ! _ . isEmpty ( child . Milestones ) ) { lastTask . Milestones = lastTask . Milestones . concat ( child . Milestones ) ; } } ) ; } } ) ; } } ) ; } param taskGroupSelection private taskSelectionRectRender ( taskGroupSelection ) { let taskSelection = taskGroupSelection . selectAll ( Selectors . SingleTask . selectorName ) . data ( ( d ) => d . tasks ) ; taskSelection . exit ( ) . remove ( ) ; const taskSelectionMerged = taskSelection . enter ( ) . append ( "g" ) . merge ( taskSelection ) ; taskSelectionMerged . classed ( Selectors . SingleTask . className , true ) ; return taskSelectionMerged ; } param task private getTaskRectWidth ( task ) { const taskIsCollapsed = _ . includes ( this . collapsedTasks , task . name ) ; return this . hasNotNullableDates && ( taskIsCollapsed || _ . isEmpty ( task . Milestones ) ) ? this . taskDurationToWidth ( task . start , task . end ) : 0 ; } param task param taskConfigHeight private drawTaskRect ( task , taskConfigHeight ) { const x = this . hasNotNullableDates ? this . timeScale ( task . start ) : 0 , y = Gantt . getBarYCoordinate ( task . index , taskConfigHeight ) + ( task . index + 1 ) * this . getResourceLabelTopMargin ( ) , width = this . getTaskRectWidth ( task ) , height = Gantt . getBarHeight ( taskConfigHeight ) , radius = Gantt . RectRound ; if ( width < 2 * radius ) { return drawNotRoundedRectByPath ( x , y , width , height ) ; } return drawRoundedRectByPath ( x , y , width + Gantt . RectRound , height , radius ) ; } param taskSelection param taskConfigHeight private taskMainRectRender ( taskSelection , taskConfigHeight ) { const highContrastModeTaskRectStroke = 1 ; let taskRect = taskSelection . selectAll ( Selectors . TaskRect . selectorName ) . data ( ( d ) => [ d ] ) ; const taskRectMerged = taskRect . enter ( ) . append ( "path" ) . merge ( taskRect ) ; taskRectMerged . classed ( Selectors . TaskRect . className , true ) ; let index = 0 , groupedTaskIndex = 0 ; taskRectMerged . attr ( "d" , ( task ) => this . drawTaskRect ( task , taskConfigHeight ) ) . attr ( "width" , ( task ) => this . getTaskRectWidth ( task ) ) . style ( "fill" , ( task ) => { if ( index === task . index ) { groupedTaskIndex ++ ; } else { groupedTaskIndex = 0 ; index = task . index ; } const url = `${ task . index }-${ groupedTaskIndex }-${ isStringNotNullEmptyOrUndefined ( task . taskType ) ? task . taskType . toString ( ) : "taskType" }` ; const encodedUrl = `task${ hashCode ( url ) }` ; return `url(#${ encodedUrl })` ; } ) ; if ( this . colorHelper . isHighContrast ) { taskRectMerged . style ( "stroke" , ( task ) => this . colorHelper . getHighContrastColor ( "foreground" , task . color ) ) . style ( "stroke-width" , highContrastModeTaskRectStroke ) ; } taskRect . exit ( ) . remove ( ) ; } param milestoneType private getMilestoneColor ( milestoneType ) { const milestone = this . viewModel . milestonesData . dataPoints . filter ( ( dataPoint ) => dataPoint . name === milestoneType ) [ 0 ] ; return this . colorHelper . getHighContrastColor ( "foreground" , milestone . color ) ; } private getMilestonePath ( milestoneType , taskConfigHeight ) { let shape ; const convertedHeight = Gantt . getBarHeight ( taskConfigHeight ) ; const milestone = this . viewModel . milestonesData . dataPoints . filter ( ( dataPoint ) => dataPoint . name === milestoneType ) [ 0 ] ; switch ( milestone . shapeType ) { case MilestoneShapeTypes . Rhombus : shape = drawDiamond ( convertedHeight ) ; break ; case MilestoneShapeTypes . Square : shape = drawRectangle ( convertedHeight ) ; break ; case MilestoneShapeTypes . Circle : shape = drawCircle ( convertedHeight ) ; } return shape ; } param taskSelection param taskConfigHeight private MilestonesRender ( taskSelection , taskConfigHeight ) { let taskMilestones = taskSelection . selectAll ( Selectors . TaskMilestone . selectorName ) . data ( ( d ) => { const nestedByDate = d3 . nest ( ) . key ( ( d ) => d . start . toDateString ( ) ) . entries ( d . Milestones ) ; let updatedMilestones = nestedByDate . map ( ( nestedObj ) => { const oneDateMilestones = nestedObj . values ; let currentMilestone = [ ... oneDateMilestones ] . pop ( ) ; const allTooltipInfo = oneDateMilestones . map ( ( milestone ) => milestone . tooltipInfo ) ; currentMilestone . tooltipInfo = allTooltipInfo . reduce ( ( a , b ) => a . concat ( b ) , [ ] ) ; return { type : currentMilestone . type , start : currentMilestone . start , taskID : d . index , tooltipInfo : currentMilestone . tooltipInfo } ; } ) ; return [ { key : d . index , values : < MilestonePath [ ] > updatedMilestones } ] ; } ) ; taskMilestones . exit ( ) . remove ( ) ; const taskMilestonesAppend = taskMilestones . enter ( ) . append ( "g" ) ; const taskMilestonesMerged = taskMilestonesAppend . merge ( taskMilestones ) ; taskMilestonesMerged . classed ( Selectors . TaskMilestone . className , true ) ; const transformForMilestone = ( id , start ) => { return SVGManipulations . translate ( this . timeScale ( start ) - Gantt . getBarHeight ( taskConfigHeight ) / 4 , Gantt . getBarYCoordinate ( id , taskConfigHeight ) + ( id + 1 ) * this . getResourceLabelTopMargin ( ) ) ; } ; let taskMilestonesSelection = taskMilestonesMerged . selectAll ( "path" ) ; let taskMilestonesSelectionData = taskMilestonesSelection . data ( milestonesData => < MilestonePath [ ] > milestonesData . values ) ; let taskMilestonesSelectionAppend = taskMilestonesSelectionData . enter ( ) . append ( "path" ) ; taskMilestonesSelectionData . exit ( ) . remove ( ) ; let taskMilestonesSelectionMerged = taskMilestonesSelectionAppend . merge ( < any > taskMilestonesSelection ) ; if ( this . hasNotNullableDates ) { taskMilestonesSelectionMerged . attr ( "d" , ( data ) => this . getMilestonePath ( data . type , taskConfigHeight ) ) . attr ( "transform" , ( data ) => transformForMilestone ( data . taskID , data . start ) ) . attr ( "fill" , ( data ) => this . getMilestoneColor ( data . type ) ) ; } this . renderTooltip ( taskMilestonesSelectionMerged ) ; } param taskSelection param taskConfigHeight private taskDaysOffRender ( taskSelection , taskConfigHeight ) { const taskDaysOffColor = this . viewModel . settings . daysOff . fill ; const taskDaysOffShow = this . viewModel . settings . daysOff . show ; taskSelection . selectAll ( Selectors . TaskDaysOff . selectorName ) . remove ( ) ; if ( taskDaysOffShow ) { let tasksDaysOff = taskSelection . selectAll ( Selectors . TaskDaysOff . selectorName ) . data ( ( d ) => { let tasksDaysOff = [ ] ; if ( ! d . children && d . daysOffList ) { for ( let i = 0 ; i < d . daysOffList . length ; i ++ ) { let currentDaysOffItem = d . daysOffList [ i ] ; let startOfLastDay = new Date ( + d . end ) ; startOfLastDay . setHours ( 0 , 0 , 0 ) ; if ( currentDaysOffItem [ 0 ] . getTime ( ) < startOfLastDay . getTime ( ) ) { tasksDaysOff . push ( { id : d . index , daysOff : d . daysOffList [ i ] } ) ; } } } return tasksDaysOff ; } ) ; const tasksDaysOffMerged = tasksDaysOff . enter ( ) . append ( "path" ) . merge ( tasksDaysOff ) ; tasksDaysOffMerged . classed ( Selectors . TaskDaysOff . className , true ) ; const getTaskRectDaysOffWidth = ( task ) => { let width = 0 ; if ( this . hasNotNullableDates ) { const startDate = task . daysOff [ 0 ] ; const startTime = startDate . getTime ( ) ; const endDate = new Date ( startTime + ( task . daysOff [ 1 ] * MillisecondsInADay ) ) ; width = this . taskDurationToWidth ( startDate , endDate ) ; } return width ; } ; const drawTaskRectDaysOff = ( task ) => { let x = this . hasNotNullableDates ? this . timeScale ( task . daysOff [ 0 ] ) : 0 , y = Gantt . getBarYCoordinate ( task . id , taskConfigHeight ) + ( task . id + 1 ) * this . getResourceLabelTopMargin ( ) , height = Gantt . getBarHeight ( taskConfigHeight ) , radius = Gantt . RectRound , width = getTaskRectDaysOffWidth ( task ) ; if ( width < radius ) { x = x - width / 2 ; } if ( width < 2 * radius ) { return drawNotRoundedRectByPath ( x , y , width , height ) ; } return drawRoundedRectByPath ( x , y , width , height , radius ) ; } ; tasksDaysOffMerged . attr ( "d" , ( task ) => drawTaskRectDaysOff ( task ) ) . style ( "fill" , taskDaysOffColor ) . attr ( "width" , ( task ) => getTaskRectDaysOffWidth ( task ) ) ; tasksDaysOff . exit ( ) . remove ( ) ; } } param taskSelection private taskProgressRender ( taskSelection ) { const taskProgressShow = this . viewModel . settings . taskCompletion . show ; let index = 0 , groupedTaskIndex = 0 ; let taskProgress = taskSelection . selectAll ( Selectors . TaskProgress . selectorName ) . data ( ( d , i ) => { const taskProgressPercentage = this . getDaysOffTaskProgressPercent ( d ) ; if ( index === d . index ) { groupedTaskIndex ++ ; } else { groupedTaskIndex = 0 ; index = d . index ; } const url = `${ d . index }-${ groupedTaskIndex }-${ isStringNotNullEmptyOrUndefined ( d . taskType ) ? d . taskType . toString ( ) : "taskType" }` ; const encodedUrl = `task${ hashCode ( url ) }` ; return [ { key : encodedUrl , values : < LinearStop [ ] > [ { completion : 0 , color : d . color } , { completion : taskProgressPercentage , color : d . color } , { completion : taskProgressPercentage , color : d . color } , { completion : 1 , color : d . color } ] } ] ; } ) ; const taskProgressMerged = taskProgress . enter ( ) . append ( "linearGradient" ) . merge ( taskProgress ) ; taskProgressMerged . classed ( Selectors . TaskProgress . className , true ) ; taskProgressMerged . attr ( "id" , ( data ) => data . key ) ; let stopsSelection = taskProgressMerged . selectAll ( "stop" ) ; let stopsSelectionData = stopsSelection . data ( gradient => < LinearStop [ ] > gradient . values ) ; stopsSelectionData . enter ( ) . append ( "stop" ) . merge ( < any > stopsSelection ) . attr ( "offset" , ( data ) => `${ data . completion * 100 }%` ) . attr ( "stop-color" , ( data ) => this . colorHelper . getHighContrastColor ( "foreground" , data . color ) ) . attr ( "stop-opacity" , ( data , index ) => ( index > 1 ) && taskProgressShow ? Gantt . NotCompletedTaskOpacity : Gantt . TaskOpacity ) ; taskProgress . exit ( ) . remove ( ) ; } param taskSelection param taskConfigHeight private taskResourceRender ( taskSelection , taskConfigHeight ) { const groupTasks = this . viewModel . settings . general . groupTasks ; let newLabelPosition = null ; if ( groupTasks && ! this . groupTasksPrevValue ) { newLabelPosition = ResourceLabelPositions . Inside ; } if ( ! groupTasks && this . groupTasksPrevValue ) { newLabelPosition = ResourceLabelPositions . Right ; } if ( newLabelPosition ) { this . host . persistProperties ( < VisualObjectInstancesToPersist > { merge : [ { objectName : "taskResource" , selector : null , properties : { position : newLabelPosition } } ] } ) ; this . viewModel . settings . taskResource . position = newLabelPosition ; newLabelPosition = null ; } this . groupTasksPrevValue = groupTasks ; const isResourcesFilled = this . viewModel . isResourcesFilled ; const taskResourceShow = this . viewModel . settings . taskResource . show ; const taskResourceColor = this . viewModel . settings . taskResource . fill ; const taskResourceFontSize = this . viewModel . settings . taskResource . fontSize ; const taskResourcePosition = this . viewModel . settings . taskResource . position ; const taskResourceFullText = this . viewModel . settings . taskResource . fullText ; const taskResourceWidthByTask = this . viewModel . settings . taskResource . widthByTask ; const isGroupedByTaskName = this . viewModel . settings . general . groupTasks ; if ( isResourcesFilled && taskResourceShow ) { let taskResource = taskSelection . selectAll ( Selectors . TaskResource . selectorName ) . data ( ( d ) => [ d ] ) ; const taskResourceMerged = taskResource . enter ( ) . append ( "text" ) . merge ( taskResource ) ; taskResourceMerged . classed ( Selectors . TaskResource . className , true ) ; taskResourceMerged . attr ( "x" , ( task ) => this . getResourceLabelXCoordinate ( task , taskConfigHeight , taskResourceFontSize , taskResourcePosition ) ) . attr ( "y" , ( task ) => Gantt . getBarYCoordinate ( task . index , taskConfigHeight ) + Gantt . getResourceLabelYOffset ( taskConfigHeight , taskResourceFontSize , taskResourcePosition ) + ( task . index + 1 ) * this . getResourceLabelTopMargin ( ) ) . text ( ( task ) => _ . isEmpty ( task . Milestones ) && task . resource || "" ) . style ( "fill" , taskResourceColor ) . style ( "font-size" , PixelConverter . fromPoint ( taskResourceFontSize ) ) ; let self = this ; let hasNotNullableDates = this . hasNotNullableDates ; const defaultWidth = Gantt . DefaultValues . ResourceWidth - Gantt . ResourceWidthPadding ; if ( taskResourceWidthByTask ) { taskResourceMerged . each ( function ( task , outerIndex ) { const width = hasNotNullableDates ? self . taskDurationToWidth ( task . start , task . end ) : 0 ; AxisHelper . LabelLayoutStrategy . clip ( d3 . select ( this ) , width , textMeasurementService . svgEllipsis ) ; } ) ; } else if ( isGroupedByTaskName ) { taskResourceMerged . each ( function ( task , outerIndex ) { const sameRowNextTaskStart = self . getSameRowNextTaskStartDate ( task , outerIndex , taskResourceMerged ) ; if ( sameRowNextTaskStart ) { let width = 0 ; if ( hasNotNullableDates ) { const startDate = taskResourcePosition === ResourceLabelPositions . Top ? task . start : task . end ; width = self . taskDurationToWidth ( startDate , sameRowNextTaskStart ) ; } AxisHelper . LabelLayoutStrategy . clip ( d3 . select ( this ) , width , textMeasurementService . svgEllipsis ) ; } else { if ( ! taskResourceFullText ) { AxisHelper . LabelLayoutStrategy . clip ( d3 . select ( this ) , defaultWidth , textMeasurementService . svgEllipsis ) ; } } } ) ; } else if ( ! taskResourceFullText ) { taskResourceMerged . each ( function ( task , outerIndex ) { AxisHelper . LabelLayoutStrategy . clip ( d3 . select ( this ) , defaultWidth , textMeasurementService . svgEllipsis ) ; } ) ; } taskResource . exit ( ) . remove ( ) ; } else { taskSelection . selectAll ( Selectors . TaskResource . selectorName ) . remove ( ) ; } } private getSameRowNextTaskStartDate ( task , index , selection ) { let sameRowNextTaskStart ; selection . each ( function ( x , i ) { if ( index !== i && x . index === task . index && x . start >= task . start && ( ! sameRowNextTaskStart || sameRowNextTaskStart < x . start ) ) { sameRowNextTaskStart = x . start ; } } ) ; return sameRowNextTaskStart ; } private static getResourceLabelYOffset ( taskConfigHeight , taskResourceFontSize , taskResourcePosition ) { const barHeight = Gantt . getBarHeight ( taskConfigHeight ) ; switch ( taskResourcePosition ) { case ResourceLabelPositions . Right : return ( barHeight / Gantt . DeviderForCalculatingCenter ) + ( taskResourceFontSize / Gantt . DeviderForCalculatingCenter ) ; case ResourceLabelPositions . Top : return - ( taskResourceFontSize / Gantt . DeviderForCalculatingPadding ) + Gantt . LabelTopOffsetForPadding ; case ResourceLabelPositions . Inside : return - ( taskResourceFontSize / Gantt . DeviderForCalculatingPadding ) + Gantt . LabelTopOffsetForPadding + barHeight / Gantt . ResourceLabelDefaultDivisionCoefficient ; } } private getResourceLabelXCoordinate ( task , taskConfigHeight , taskResourceFontSize , taskResourcePosition ) { if ( ! this . hasNotNullableDates ) { return 0 ; } const barHeight = Gantt . getBarHeight ( taskConfigHeight ) ; switch ( taskResourcePosition ) { case ResourceLabelPositions . Right : return this . timeScale ( task . end ) + ( taskResourceFontSize / 2 ) + Gantt . RectRound ; case ResourceLabelPositions . Top : return this . timeScale ( task . start ) + Gantt . RectRound ; case ResourceLabelPositions . Inside : return this . timeScale ( task . start ) + barHeight / ( 2 * Gantt . ResourceLabelDefaultDivisionCoefficient ) + Gantt . RectRound ; } } param taskIndex private getTaskLabelCoordinateY ( taskIndex ) { const settings = this . viewModel . settings ; const fontSize = + settings . taskLabels . fontSize ; const taskConfigHeight = settings . taskConfig . height || DefaultChartLineHeight ; const taskYCoordinate = taskConfigHeight * taskIndex ; const barHeight = Gantt . getBarHeight ( taskConfigHeight ) ; return taskYCoordinate + ( barHeight + Gantt . BarHeightMargin - ( taskConfigHeight - fontSize ) / Gantt . ChartLineHeightDivider ) ; } param task param durationUnit private getDaysOffTaskProgressPercent ( task ) { if ( this . viewModel . settings . daysOff . show ) { if ( task . daysOffList && task . daysOffList . length && task . duration && task . completion ) { let durationUnit = this . viewModel . settings . general . durationUnit ; if ( task . wasDowngradeDurationUnit ) { durationUnit = DurationHelper . downgradeDurationUnit ( durationUnit , task . duration ) ; } const startTime = task . start . getTime ( ) ; const progressLength = ( task . end . getTime ( ) - startTime ) * task . completion ; const currentProgressTime = new Date ( startTime + progressLength ) . getTime ( ) ; let daysOffFiltered = task . daysOffList . filter ( ( date ) => startTime <= date [ 0 ] . getTime ( ) && date [ 0 ] . getTime ( ) <= currentProgressTime ) ; let extraDuration = Gantt . calculateExtraDurationDaysOff ( daysOffFiltered , task . end , task . start , + this . viewModel . settings . daysOff . firstDayOfWeek , durationUnit ) ; const extraDurationPercentage = extraDuration / task . duration ; return task . completion + extraDurationPercentage ; } } return task . completion ; } param lineNumber param lineHeight private static getBarYCoordinate ( lineNumber , lineHeight ) { return ( lineHeight * lineNumber ) + PaddingTasks ; } param lineHeight private static getBarHeight ( lineHeight ) { return lineHeight / Gantt . ChartLineProportion ; } /**
     * Get the margin that added to task rects and task category labels
     *
     * depends on resource label position and resource label font size
     */ private getResourceLabelTopMargin ( ) { const isResourcesFilled = this . viewModel . isResourcesFilled ; const taskResourceShow = this . viewModel . settings . taskResource . show ; const taskResourceFontSize = this . viewModel . settings . taskResource . fontSize ; const taskResourcePosition = this . viewModel . settings . taskResource . position ; let margin = 0 ; if ( isResourcesFilled && taskResourceShow && taskResourcePosition === ResourceLabelPositions . Top ) { margin = Number ( taskResourceFontSize ) + Gantt . LabelTopOffsetForPadding ; } return margin ; } param start param end private taskDurationToWidth ( start , end ) { return this . timeScale ( end ) - this . timeScale ( start ) ; } private static getTooltipForMilestoneLine ( formattedDate , localizationManager , dateTypeSettings , milestoneTitle , milestoneCategoryName ? ) { let result = [ ] ; for ( let i = 0 ; i < milestoneTitle . length ; i ++ ) { if ( ! milestoneTitle [ i ] ) { switch ( dateTypeSettings . type ) { case DateTypes . Second : case DateTypes . Minute : case DateTypes . Hour : milestoneTitle [ i ] = localizationManager . getDisplayName ( "Visual_Label_Now" ) ; break ; default : milestoneTitle [ i ] = localizationManager . getDisplayName ( "Visual_Label_Today" ) ; } } if ( milestoneCategoryName ) { result . push ( { displayName : localizationManager . getDisplayName ( "Visual_Milestone_Name" ) , value : milestoneCategoryName [ i ] } ) ; } result . push ( { displayName : < string > milestoneTitle [ i ] , value : formattedDate } ) ; } return result ; } param tasks param milestoneTitle param timestamp private createMilestoneLine ( tasks , timestamp = Date . now ( ) , milestoneTitle ? ) { if ( ! this . hasNotNullableDates ) { return ; } let todayColor = this . viewModel . settings . dateType . todayColor ; let milestoneDates = [ new Date ( timestamp ) ] ; tasks . forEach ( ( task ) => { const subtasks = task . tasks ; subtasks . forEach ( ( task ) => { if ( ! _ . isEmpty ( task . Milestones ) ) { task . Milestones . forEach ( ( milestone ) => { if ( ! _ . includes ( milestoneDates , milestone . start ) ) { milestoneDates . push ( milestone . start ) ; } } ) ; } } ) ; } ) ; let line = [ ] ; const dateTypeSettings = this . viewModel . settings . dateType ; milestoneDates . forEach ( ( date ) => { const title = date === this . timeScale ( timestamp ) ? milestoneTitle : "Milestone" ; const lineOptions = { x1 : this . timeScale ( date ) , y1 : Gantt . MilestoneTop , x2 : this . timeScale ( date ) , y2 : this . getMilestoneLineLength ( tasks . length ) , tooltipInfo : Gantt . getTooltipForMilestoneLine ( date . toLocaleDateString ( ) , this . localizationManager , dateTypeSettings , [ title ] ) } ; line . push ( lineOptions ) ; } ) ; let chartLineSelection = this . chartGroup . selectAll ( Selectors . ChartLine . selectorName ) . data ( line ) ; const chartLineSelectionMerged = chartLineSelection . enter ( ) . append ( "line" ) . merge ( chartLineSelection ) ; chartLineSelectionMerged . classed ( Selectors . ChartLine . className , true ) ; chartLineSelectionMerged . attr ( "x1" , ( line ) => line . x1 ) . attr ( "y1" , ( line ) => line . y1 ) . attr ( "x2" , ( line ) => line . x2 ) . attr ( "y2" , ( line ) => line . y2 ) . style ( "stroke" , ( line ) => { let color = line . x1 === this . timeScale ( timestamp ) ? todayColor : Gantt . DefaultValues . MilestoneLineColor ; return this . colorHelper . getHighContrastColor ( "foreground" , color ) ; } ) ; this . renderTooltip ( chartLineSelectionMerged ) ; chartLineSelection . exit ( ) . remove ( ) ; } private scrollToMilestoneLine ( axisLength , timestamp = Date . now ( ) ) { let scrollValue = this . timeScale ( new Date ( timestamp ) ) ; scrollValue -= scrollValue > ScrollMargin ? ScrollMargin : 0 ; if ( axisLength > scrollValue ) { ( this . body . node ( ) as SVGSVGElement ) . querySelector ( Selectors . Body . selectorName ) . scrollLeft = scrollValue ; } } private renderTooltip ( selection ) { this . tooltipServiceWrapper . addTooltip ( selection , ( tooltipEvent ) => { return tooltipEvent . data . tooltipInfo ; } ) ; } private updateElementsPositions ( margin ) { let settings = this . viewModel . settings ; const taskLabelsWidth = settings . taskLabels . show ? settings . taskLabels . width : 0 ; let translateXValue = taskLabelsWidth + margin . left + Gantt . SubtasksLeftMargin ; this . chartGroup . attr ( "transform" , SVGManipulations . translate ( translateXValue , margin . top ) ) ; let translateYValue = Gantt . TaskLabelsMarginTop + ( this . ganttDiv . node ( ) as SVGSVGElement ) . scrollTop ; this . axisGroup . attr ( "transform" , SVGManipulations . translate ( translateXValue , translateYValue ) ) ; translateXValue = ( this . ganttDiv . node ( ) as SVGSVGElement ) . scrollLeft ; this . lineGroup . attr ( "transform" , SVGManipulations . translate ( translateXValue , 0 ) ) ; this . collapseAllGroup . attr ( "transform" , SVGManipulations . translate ( 0 , margin . top / 4 ) ) ; } private getMilestoneLineLength ( numOfTasks ) { return numOfTasks * ( ( this . viewModel . settings . taskConfig . height || DefaultChartLineHeight ) + ( 1 + numOfTasks ) * this . getResourceLabelTopMargin ( ) / 2 ) ; } public static downgradeDurationUnitIfNeeded ( tasks , durationUnit ) { const downgradedDurationUnitTasks = tasks . filter ( t => t . wasDowngradeDurationUnit ) ; if ( downgradedDurationUnitTasks . length ) { let maxStepDurationTransformation = 0 ; downgradedDurationUnitTasks . forEach ( x => maxStepDurationTransformation = x . stepDurationTransformation > maxStepDurationTransformation ? x . stepDurationTransformation : maxStepDurationTransformation ) ; tasks . filter ( x => x . stepDurationTransformation !== maxStepDurationTransformation ) . forEach ( task => { task . duration = DurationHelper . transformDuration ( task . duration , durationUnit , maxStepDurationTransformation ) ; task . stepDurationTransformation = maxStepDurationTransformation ; task . wasDowngradeDurationUnit = true ; } ) ; } } public enumerateObjectInstances ( options ) { const settings = this . viewModel && this . viewModel . settings || GanttSettings . getDefault ( ) as GanttSettings ; const instanceEnumeration = GanttSettings . enumerateObjectInstances ( settings , options ) ; if ( options . objectName === Gantt . MilestonesPropertyIdentifier . objectName ) { this . enumerateMilestones ( instanceEnumeration ) ; } if ( options . objectName === Gantt . LegendPropertyIdentifier . objectName ) { this . enumerateLegend ( instanceEnumeration ) ; } if ( options . objectName === Gantt . CollapsedTasksPropertyIdentifier . objectName ) { return ; } if ( ! this . viewModel . isResourcesFilled && options . objectName === Gantt . TaskResourcePropertyIdentifier . objectName ) { return ; } return ( instanceEnumeration as VisualObjectInstanceEnumerationObject ) . instances || [ ] ; } private enumerateMilestones ( instanceEnumeration ) { if ( this . viewModel && ! this . viewModel . isDurationFilled && ! this . viewModel . isEndDateFillled ) { return ; } const dataPoints = this . viewModel && this . viewModel . milestonesData . dataPoints ; if ( ! dataPoints || ! dataPoints . length ) { return ; } const milestonesWithoutDublicates = Gantt . getUniqueMilestones ( dataPoints ) ; for ( let uniqMilestones in milestonesWithoutDublicates ) { const milestone = milestonesWithoutDublicates [ uniqMilestones ] ; this . addAnInstanceToEnumeration ( instanceEnumeration , { displayName : `${ milestone . name } color` , objectName : Gantt . MilestonesPropertyIdentifier . objectName , selector : ColorHelper . normalizeSelector ( ( milestone . identity as ISelectionId ) . getSelector ( ) , false ) , properties : { fill : { solid : { color : milestone . color } } } } ) ; this . addAnInstanceToEnumeration ( instanceEnumeration , { displayName : `${ milestone . name } shape` , objectName : Gantt . MilestonesPropertyIdentifier . objectName , selector : ColorHelper . normalizeSelector ( ( milestone . identity as ISelectionId ) . getSelector ( ) , false ) , properties : { shapeType : milestone . shapeType } } ) ; } } private enumerateLegend ( instanceEnumeration ) { if ( this . viewModel && ! this . viewModel . isDurationFilled && ! this . viewModel . isEndDateFillled ) { return ; } const dataPoints = this . viewModel && this . viewModel . legendData . dataPoints ; if ( ! dataPoints || ! dataPoints . length ) { return ; } dataPoints . forEach ( ( dataPoint ) => { this . addAnInstanceToEnumeration ( instanceEnumeration , { displayName : dataPoint . label , objectName : Gantt . LegendPropertyIdentifier . objectName , selector : ColorHelper . normalizeSelector ( ( dataPoint . identity as ISelectionId ) . getSelector ( ) , false ) , properties : { fill : { solid : { color : dataPoint . color } } } } ) ; } ) ; } private addAnInstanceToEnumeration ( instanceEnumeration , instance ) { if ( ( instanceEnumeration as VisualObjectInstanceEnumerationObject ) . instances ) { ( instanceEnumeration as VisualObjectInstanceEnumerationObject ) . instances . push ( instance ) ; } else { ( instanceEnumeration as VisualObjectInstance [ ] ) . push ( instance ) ; } } }