{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "DL0K-pbi PMO_toolkit: Gantt chart, modified from David Bacci gantt, this gantt work with the weekly gantt excel document as a data source connected to power bi https://www.linkedin.com/in/devon-locher/ | https://github.com/DL0K-pbi/PMO_toolkit",
  "autosize": "pad",
  "padding": {
    "left": 5,
    "right": 0,
    "top": 5,
    "bottom": 0
  },
  "signals": [
    {
      "name": "height",
      "update": "pbiContainerHeight-65"
    },
    {
      "name": "width",
      "update": "pbiContainerWidth"
    },
    {
      "name": "showTooltips",
      "value": true
    },
    {
      "name": "initDate",
      "value": "project",
      "description": "The initial date shown in the Gantt. Can be either 'today' or 'project'"
    },
    {
      "name": "showButtons",
      "value": true
    },
    {
      "name": "showDomainSpanLabel",
      "value": false
    },
    {
      "name": "startGrain",
      "value": "Months",
      "description": "Days, Months, Years or All"
    },
    {
      "name": "initPhaseState",
      "value": "openRows",
      "description": "openRows or closeRows"
    },
    {
      "name": "initColumnState",
      "value": "openColumns",
      "description": "openColumns or closeColumns"
    },
    {
      "name": "textColour",
      "value": "#000000"
    },
    {
      "name": "colours",
      "value": {
        "dark": [
          "#377eb9",
          "#974ea2",
          "#ff7f00",
          "#8da0cb",
          "#ffd92f",
          "#66c2a5",
          "#e78ac3",
          "#DABFA3"
        ],
        "light": [
          "#a5c8e4",
          "#d3b0d9",
          "#ffc580",
          "#c7d7ec",
          "#fff2b2",
          "#b2dfdb",
          "#f4cae4",
          "#F1E5D7"
        ]
      }
    },
    {
      "name": "statusColumn",
      "value": {
        "domain": [
          "Not Started",
          "In Progress",
          "Complete",
          "Blocked"
        ],
        "range": [
          "Not Started",
          "In Progress",
          "Complete",
          "Blocked"
        ],
        "font": [
          "#000000",
          "#000000",
          "#000000",
          "#000000"
        ],
        "background": [
          "#FFFFFF",
          "#92D050",
          "#A6A6A6",
          "#FF9812"
        ]
      }
    },
    {
      "name": "yRowHeight",
      "value": 25,
      "description": "Height in pixels"
    },
    {
      "name": "yRowPadding",
      "value": 0.22,
      "description": "Row padding as % of yRowHeight (each side)"
    },
    {
      "name": "yPaddingInner",
      "init": "yRowPadding * yRowHeight"
    },
    {
      "name": "taskColumnWidth",
      "init": "155+columnPadding"
    },
    {
      "name": "startColumnWidth",
      "init": "45+columnPadding"
    },
    {
      "name": "endColumnWidth",
      "init": "45+columnPadding"
    },
    {
      "name": "daysColumnWidth",
      "init": "35+columnPadding"
    },
    {
      "name": "statusColumnWidth",
      "init": "statusColumnPresent?47+columnPadding:0"
    },
    {
      "name": "progressColumnWidth",
      "init": "55+columnPadding"
    },
    {
      "name": "columnPadding",
      "value": 15
    },
    {
      "name": "statusColumnPresent",
      "init": "data('dataset')[0]['status']!=null"
    },
    {
      "name": "oneDay",
      "init": "1000*60*60*24"
    },
    {
      "name": "timeoffset",
      "init": "timezoneoffset(data('dataset')[0]['start']) * 60 *1000"
    },
    {
      "name": "dayBandwidth",
      "update": "scale('x', timeOffset('day', datetime(2000,1,1),1)) - scale('x', datetime(2000,1,1))"
    },
    {
      "name": "dayBandwidthRound",
      "update": "(round(dayBandwidth *100)/100)"
    },
    {
      "name": "minDayBandwidth",
      "value": 20
    },
    {
      "name": "minMonthBandwidth",
      "value": 3
    },
    {
      "name": "minYearBandwidth",
      "value": 0.95
    },
    {
      "name": "milestoneSymbolSize",
      "value": 400
    },
    {
      "name": "arrowSymbolSize",
      "value": 70
    },
    {
      "name": "phaseSymbolHeight",
      "init": "bandwidth('y')-yPaddingInner-5"
    },
    {
      "name": "phaseSymbolWidth",
      "value": 10
    },
    {
      "name": "columnsWidth",
      "update": "!showColumns?taskColumnWidth: taskColumnWidth+startColumnWidth+endColumnWidth+daysColumnWidth+statusColumnWidth+progressColumnWidth"
    },
    {
      "name": "ganttWidth",
      "update": "width-columnsWidth-minDayBandwidth"
    },
    {
      "name": "dayExt",
      "init": "initDate=='project'? [data('xExt')[0]['s']-oneDay,data('xExt')[0]['s']+ ((ganttWidth-minDayBandwidth)/minDayBandwidth)*oneDay]:[today-oneDay,today+ ((ganttWidth-minDayBandwidth)/minDayBandwidth)*oneDay]"
    },
    {
      "name": "monthExt",
      "init": "initDate=='project'? [data('xExt')[0]['s']-oneDay ,data('xExt')[0]['s'] + ganttWidth/2*oneDay]:[today-(oneDay*15) ,today + ganttWidth/2*oneDay]"
    },
    {
      "name": "yearExt",
      "init": "initDate=='project'? [data('xExt')[0]['s']-oneDay,data('xExt')[0]['s'] + ganttWidth/0.35*oneDay]:[today-(oneDay*180),today + ganttWidth/0.35*oneDay]"
    },
    {
      "name": "allExt",
      "init": "[data('xExt')[0]['s']-oneDay,data('xExt')[0]['e']+oneDay*9]"
    },
    {
      "name": "xExt",
      "update": "startGrain=='All'?allExt:startGrain=='Years'?yearExt:startGrain=='Months'?monthExt:dayExt"
    },
    {
      "name": "today",
      "init": "+datetime(year(now()),month(now()),date(now()))"
    },
    {
      "name": "todayRule",
      "init": "timeFormat(today,'%m/%d/%y')"
    },
    {
      "name": "zoom",
      "value": 1,
      "on": [
        {
          "events": "wheel!",
          "force": true,
          "update": "x()>columnsWidth?pow(1.001, (event.deltaY) * pow(16, event.deltaMode)):1"
        }
      ]
    },
    {
      "name": "xDomMinSpan",
      "update": "span(dayExt)"
    },
    {
      "name": "xDomMaxSpan",
      "update": "round((ganttWidth/0.13)*oneDay)"
    },
    {
      "name": "xDom",
      "init": "xExt",
      "on": [
        {
          "events": {
            "signal": "xDomPre"
          },
          "update": "span(xDomPre)<xDomMinSpan?[anchor + (xDom[0] - anchor) * (zoom*(xDomMinSpan/span(xDomPre))), anchor + (xDom[1] - anchor) * (zoom*(xDomMinSpan/span(xDomPre)))]:span(xDomPre)>xDomMaxSpan?[anchor + (xDom[0] - anchor) * (zoom*(xDomMaxSpan/span(xDomPre))), anchor + (xDom[1] - anchor) * (zoom*(xDomMaxSpan/span(xDomPre)))] :xDomPre"
        },
        {
          "events": {
            "signal": "delta"
          },
          "update": "[xCur[0] + span(xCur) * delta[0] / width, xCur[1] + span(xCur) * delta[0] / width]"
        },
        {
          "events": "dblclick",
          "update": "xExt"
        },
        {
          "events": "@buttonMarks:click",
          "update": "datum.name=='All'?allExt:datum.name=='Years'?yearExt:datum.name=='Months'?monthExt:datum.name=='Days'?dayExt:xDom"
        },
        {
          "events": {
            "signal": "ganttWidth"
          },
          "update": "[xDom[0],xDom[0]+((ganttWidth/ganttWidthOld)*span(xDom))]"
        }
      ]
    },
    {
      "name": "ganttWidthOld",
      "init": "ganttWidth",
      "on": [
        {
          "events": {
            "signal": "showColumns"
          },
          "update": "ganttWidth==ganttWidthOld?ganttWidthOld:ganttWidth"
        }
      ]
    },
    {
      "name": "scaledHeight",
      "update": "data('yScale').length * yRowHeight"
    },
    {
      "name": "yRange",
      "update": "[yRange!=null?yRange[0]:0,yRange!=null?yRange[0]+scaledHeight:scaledHeight]",
      "on": [
        {
          "events": [
            {
              "signal": "delta"
            }
          ],
          "update": "clampRange( [yCur[0] + span(yCur) * delta[1] / scaledHeight, yCur[1] + span(yCur) * delta[1] / scaledHeight],height>=scaledHeight?0: height-scaledHeight,height>=scaledHeight?height:scaledHeight)"
        },
        {
          "events": "dblclick",
          "update": "[0,scaledHeight]"
        },
        {
          "events": {
            "signal": "closeRowsAll"
          },
          "update": "closeRowsAll?[0, scaledHeight]:yRange"
        }
      ]
    },
    {
      "name": "xDomPre",
      "value": [
        0,
        0
      ],
      "on": [
        {
          "events": {
            "signal": "zoom"
          },
          "update": "[anchor + (xDom[0] - anchor) * zoom, anchor + (xDom[1] - anchor) * zoom]"
        }
      ]
    },
    {
      "name": "anchor",
      "value": 0,
      "on": [
        {
          "events": "wheel",
          "update": "+invert('x', x()-columnsWidth)"
        }
      ]
    },
    {
      "name": "xCur",
      "value": [
        0,
        0
      ],
      "on": [
        {
          "events": "pointerdown",
          "update": "slice(xDom)"
        }
      ]
    },
    {
      "name": "yCur",
      "value": [
        0,
        0
      ],
      "on": [
        {
          "events": "pointerdown",
          "update": "slice(yRange)"
        }
      ]
    },
    {
      "name": "delta",
      "value": [
        0,
        0
      ],
      "on": [
        {
          "events": [
            {
              "source": "window",
              "type": "pointermove",
              "consume": true,
              "between": [
                {
                  "type": "pointerdown"
                },
                {
                  "source": "window",
                  "type": "pointerup"
                }
              ]
            }
          ],
          "update": "down ? [down[0]-x(), y()-down[1]] : [0,0]"
        }
      ]
    },
    {
      "name": "down",
      "value": null,
      "on": [
        {
          "events": "pointerdown",
          "update": "xy()"
        },
        {
          "events": "pointerup",
          "update": "null"
        }
      ]
    },
    {
      "name": "phaseClicked",
      "value": null,
      "on": [
        {
          "events": "@taskSelector:click,@phaseOutline:click",
          "update": " yCur[0]==yRange[0] && yCur[1]==yRange[1]&& xCur[0]===xDom[0]&& xCur[1]===xDom[1] && datum.phase==datum.task?  {phase: datum.phase}:null",
          "force": true
        },
        {
          "events": "@taskTooltips:click",
          "update": " yCur[0]==yRange[0] && yCur[1]==yRange[1]&& xCur[0]===xDom[0]&& xCur[1]===xDom[1] && datum.datum.phase==datum.datum.task?  {phase: datum.datum.phase}:null",
          "force": true
        }
      ]
    },
    {
      "name": "itemHovered",
      "value": {
        "id": "",
        "dependencies": []
      },
      "on": [
        {
          "events": "@taskSelector:mouseover,@phaseOutline:mouseover,@milestoneSymbols:mouseover,@taskBars:mouseover,@taskNames:mouseover,@taskLabels:mouseover",
          "update": "{'id': datum.id, 'dependencies':split(datum.dependencies,',')}"
        },
        {
          "events": "@taskTooltips:mouseover",
          "update": "{'id': toString(datum.datum.id), 'dependencies':split(datum.datum.dependencies,',')}"
        },
        {
          "events": "@taskSelector:mouseout,@phaseOutline:mouseout,@milestoneSymbols:mouseout,@taskBars:mouseout,@taskNames:mouseout,@taskLabels:mouseout,@taskTooltips:mouseout",
          "update": "{'id': '', 'dependencies':[]}"
        }
      ]
    },
    {
      "name": "hover",
      "value": "",
      "on": [
        {
          "events": "@buttonMarks:pointerover",
          "update": "datum.name?datum.name:''",
          "force": true
        },
        {
          "events": "@buttonMarks:pointerout",
          "update": "''",
          "force": true
        }
      ]
    },
    {
      "name": "showColumns",
      "update": "initColumnState=='openColumns'?true:false",
      "on": [
        {
          "events": "@buttonMarks:click",
          "update": "datum.name=='closeColumns'?false:datum.name=='openColumns'?true:showColumns",
          "force": true
        }
      ]
    },
    {
      "name": "closeRowsAll",
      "on": [
        {
          "events": "@buttonMarks:click",
          "update": "datum.name=='closeRows'?true:false",
          "force": true
        }
      ]
    },
    {
      "name": "openRowsAll",
      "on": [
        {
          "events": "@buttonMarks:click",
          "update": "datum.name=='openRows'?true:false",
          "force": true
        }
      ]
    }
  ],
  "data": [
    {
      "name": "dataset"
    },
    {
      "name": "input",
      "source": "dataset",
      "transform": [
        {
          "type": "formula",
          "as": "start",
          "expr": "+datetime(year(datum.start),month(datum.start),date(datum.start),hours(datum.start),minutes(datum.start))"
        },
        {
          "type": "formula",
          "as": "end",
          "expr": "+datetime(year(datum.end),month(datum.end),date(datum.end),hours(datum.end),minutes(datum.end))"
        },
        {
          "type": "formula",
          "as": "labelEnd",
          "expr": "datum.end"
        },
        {
          "type": "formula",
          "as": "end",
          "expr": "datetime(+datum.end+oneDay)"
        },
        {
          "type": "formula",
          "as": "days",
          "expr": "round(((datum.end-datum.start)/oneDay)*10)/10"
        },
        {
          "type": "formula",
          "as": "completionLabel",
          "expr": "datum.completion+'%'"
        },
        {
          "type": "window",
          "sort": {
            "field": "start",
            "order": "ascending"
          },
          "ops": [
            "rank"
          ],
          "as": [
            "taskSort"
          ],
          "groupby": [
            "phase"
          ]
        },
        {
          "type": "formula",
          "as": "start",
          "expr": "+datum.start"
        },
        {
          "type": "formula",
          "as": "end",
          "expr": "+datum.end"
        }
      ]
    },
    {
      "name": "phases",
      "source": "input",
      "transform": [
        {
          "type": "aggregate",
          "fields": [
            "start",
            "end",
            "completion",
            "task",
            "completion",
            "labelEnd"
          ],
          "ops": [
            "min",
            "max",
            "sum",
            "count",
            "mean",
            "max"
          ],
          "as": [
            "start",
            "end",
            "sum",
            "count",
            "completion",
            "labelEnd"
          ],
          "groupby": [
            "phase"
          ]
        },
        {
          "type": "formula",
          "as": "task",
          "expr": "datum.phase"
        },
        {
          "type": "formula",
          "as": "taskSort",
          "expr": "0"
        },
        {
          "type": "formula",
          "as": "completion",
          "expr": "round(datum.completion)"
        },
        {
          "type": "formula",
          "as": "days",
          "expr": "round(((datum.end-datum.start)/oneDay)*10)/10"
        },
        {
          "type": "window",
          "sort": {
            "field": "start",
            "order": "ascending"
          },
          "ops": [
            "row_number",
            "row_number"
          ],
          "as": [
            "phaseSort",
            "id"
          ]
        },
        {
          "type": "formula",
          "as": "id",
          "expr": "length(data('input'))+datum.id+'^^^^^'"
        }
      ]
    },
    {
      "name": "collapsedPhases",
      "on": [
        {
          "trigger": "phaseClicked",
          "toggle": "phaseClicked"
        },
        {
          "trigger": "initPhaseState",
          "insert": "initPhaseState=='closeRows'? data('phases'):null"
        },
        {
          "trigger": "closeRowsAll",
          "remove": true
        },
        {
          "trigger": "closeRowsAll",
          "insert": "data('phases')"
        },
        {
          "trigger": "openRowsAll",
          "remove": true
        }
      ]
    },
    {
      "name": "phasePaths",
      "source": "phases",
      "transform": [
        {
          "type": "formula",
          "as": "phasePath",
          "expr": "'M ' + scale('x', datum.start)+' '  +   (scale('y', datum.id)+yPaddingInner) + ' H ' +  scale('x', datum.end)+' '   + ' v ' +  phaseSymbolHeight + ' L ' +  (scale('x', datum.end) - phaseSymbolWidth) +' '  +   (scale('y', datum.id)+yPaddingInner+phaseSymbolHeight/2 ) + ' L ' +  (scale('x', datum.start)+phaseSymbolWidth) + ' '  +   (scale('y', datum.id)+yPaddingInner+phaseSymbolHeight/2) + ' L ' +  (scale('x', datum.start)) + ' '  +   (scale('y', datum.id)+ yPaddingInner+phaseSymbolHeight) + ' z'"
        }
      ]
    },
    {
      "name": "tasks",
      "source": "input",
      "transform": [
        {
          "type": "filter",
          "expr": "datum.milestone != true"
        },
        {
          "type": "filter",
          "expr": "!indata('collapsedPhases', 'phase', datum.phase)"
        }
      ]
    },
    {
      "name": "milestones",
      "source": "input",
      "transform": [
        {
          "type": "filter",
          "expr": "datum.milestone == true"
        },
        {
          "type": "filter",
          "expr": "!indata('collapsedPhases', 'phase', datum.phase)"
        }
      ]
    },
    {
      "name": "yScale",
      "source": [
        "tasks",
        "phases",
        "milestones"
      ],
      "transform": [
        {
          "type": "lookup",
          "from": "phases",
          "key": "phase",
          "values": [
            "phaseSort"
          ],
          "fields": [
            "phase"
          ]
        },
        {
          "type": "window",
          "sort": {
            "field": [
              "phaseSort",
              "taskSort"
            ],
            "order": [
              "ascending",
              "ascending"
            ]
          },
          "ops": [
            "row_number"
          ],
          "as": [
            "finalSort"
          ]
        }
      ]
    },
    {
      "name": "xExt",
      "source": "input",
      "transform": [
        {
          "type": "aggregate",
          "fields": [
            "start",
            "end"
          ],
          "ops": [
            "min",
            "max"
          ],
          "as": [
            "s",
            "e"
          ]
        },
        {
          "type": "formula",
          "as": "days",
          "expr": "(datum.e-datum.s)/oneDay"
        }
      ]
    },
    {
      "name": "weekends",
      "transform": [
        {
          "type": "sequence",
          "start": 0,
          "stop": {
            "signal": "dayBandwidthRound>=minMonthBandwidth? span(xDom)/oneDay:0"
          },
          "as": "sequence"
        },
        {
          "type": "formula",
          "as": "start",
          "expr": "(+datetime(year(xDom[0]),month(xDom[0]),date(xDom[0]))-timeoffset)   +(oneDay*datum.sequence)"
        },
        {
          "type": "filter",
          "expr": "day(datum.start) == 6 || day(datum.start) == 0 "
        },
        {
          "type": "formula",
          "as": "end",
          "expr": "datetime(+datum.start+(oneDay))"
        }
      ]
    },
    {
      "name": "taskDependencyArrows",
      "source": "yScale",
      "transform": [
        {
          "type": "filter",
          "expr": "isValid(datum.dependencies) && datum.dependencies!='' "
        }
      ]
    },
    {
      "name": "phaseDependencyArrows",
      "source": "input",
      "transform": [
        {
          "type": "filter",
          "expr": "indata('collapsedPhases', 'phase', datum.phase) "
        },
        {
          "type": "joinaggregate",
          "fields": [
            "id",
            "start"
          ],
          "ops": [
            "values",
            "min"
          ],
          "as": [
            "allPhaseIds",
            "start"
          ],
          "groupby": [
            "phase"
          ]
        },
        {
          "type": "formula",
          "as": "id",
          "expr": "toString(datum.id)"
        },
        {
          "type": "formula",
          "as": "allPhaseIds",
          "expr": "pluck(datum.allPhaseIds, 'id')"
        },
        {
          "type": "formula",
          "as": "dependencies",
          "expr": "split(datum.dependencies,',')"
        },
        {
          "type": "flatten",
          "fields": [
            "dependencies"
          ]
        },
        {
          "type": "formula",
          "as": "internalDependenciesIndex",
          "expr": "indexof(datum.allPhaseIds,datum.dependencies)"
        },
        {
          "type": "formula",
          "as": "milestone",
          "expr": "null"
        },
        {
          "type": "filter",
          "expr": "datum.dependencies!='null' && datum.dependencies!='' && datum.dependencies!='undefined' && datum.internalDependenciesIndex == -1 "
        },
        {
          "type": "lookup",
          "from": "phases",
          "key": "phase",
          "values": [
            "id"
          ],
          "fields": [
            "phase"
          ],
          "as": [
            "id"
          ]
        }
      ]
    },
    {
      "name": "dependencyArrows",
      "source": [
        "taskDependencyArrows",
        "phaseDependencyArrows"
      ]
    },
    {
      "name": "dependencyLines",
      "source": [
        "yScale",
        "phaseDependencyArrows"
      ],
      "transform": [
        {
          "type": "filter",
          "expr": "isValid(datum.dependencies) && datum.dependencies!='' "
        },
        {
          "type": "formula",
          "as": "dependencies",
          "expr": "split(datum.dependencies,',')"
        },
        {
          "type": "flatten",
          "fields": [
            "dependencies"
          ]
        },
        {
          "type": "lookup",
          "from": "input",
          "key": "id",
          "values": [
            "id",
            "end",
            "phase",
            "milestone"
          ],
          "fields": [
            "dependencies"
          ],
          "as": [
            "sourceId",
            "sourceEnd",
            "sourcePhase",
            "sourceMilestone"
          ]
        },
        {
          "type": "lookup",
          "from": "phases",
          "key": "phase",
          "values": [
            "id",
            "end"
          ],
          "fields": [
            "sourcePhase"
          ],
          "as": [
            "sourcePhaseId",
            "sourcePhaseEnd"
          ]
        },
        {
          "type": "formula",
          "as": "sourceId",
          "expr": "indata('collapsedPhases', 'phase', datum.sourcePhase) == true?datum.sourcePhaseId:datum.sourceId"
        },
        {
          "type": "formula",
          "as": "sourceEnd",
          "expr": "indata('collapsedPhases', 'phase', datum.sourcePhase) == true?datum.sourcePhaseEnd:datum.sourceEnd"
        },
        {
          "type": "formula",
          "as": "plottedStart",
          "expr": "datum.milestone == null || datum.milestone == false?scale('x',datum.start)- sqrt(arrowSymbolSize) - 1:(scale('x',datum.start) +(dayBandwidth/2) - (sqrt(milestoneSymbolSize)/2) - sqrt(arrowSymbolSize)) - 1"
        },
        {
          "type": "formula",
          "as": "plottedSourceEnd",
          "expr": "scale('x',datum.sourceEnd) - (dayBandwidth/2) "
        },
        {
          "type": "formula",
          "as": "a",
          "expr": "[datum.milestone == null || datum.milestone == false?scale('x',datum.start):scale('x',datum.start) +(dayBandwidth/2) - (sqrt(milestoneSymbolSize)/2)  ,scale('y', datum.id)+bandwidth('y')/2 ]"
        },
        {
          "type": "formula",
          "as": "b",
          "expr": "[datum.plottedStart >= datum.plottedSourceEnd?datum.plottedSourceEnd :datum.plottedStart ,scale('y', datum.id)+bandwidth('y')/2]"
        },
        {
          "type": "formula",
          "as": "c",
          "expr": "[datum.plottedSourceEnd,scale('y',datum.sourceId)+bandwidth('y')/2]"
        },
        {
          "type": "formula",
          "as": "d",
          "expr": "[datum.plottedStart > datum.plottedSourceEnd?null:datum.plottedStart ,datum.plottedStart > datum.plottedSourceEnd?null:scale('y',datum.sourceId)+(bandwidth('y'))]"
        },
        {
          "type": "formula",
          "as": "e",
          "expr": "[datum.plottedStart > datum.plottedSourceEnd?null:datum.plottedSourceEnd,datum.plottedStart > datum.plottedSourceEnd?null:scale('y',datum.sourceId)+(bandwidth('y'))]"
        },
        {
          "type": "fold",
          "fields": [
            "a",
            "b",
            "d",
            "e",
            "c"
          ]
        },
        {
          "type": "filter",
          "expr": "datum.value[0] != null"
        },
        {
          "type": "formula",
          "as": "value0",
          "expr": "datum.value[0]"
        },
        {
          "type": "formula",
          "as": "value1",
          "expr": "datum.value[1]"
        },
        {
          "type": "window",
          "ops": [
            "row_number"
          ],
          "as": [
            "duplicates"
          ],
          "groupby": [
            "id",
            "sourceId",
            "value0",
            "value1"
          ]
        },
        {
          "type": "filter",
          "expr": "datum.duplicates == 1"
        }
      ]
    },
    {
      "name": "buttons",
      "values": [
        {
          "side": "left",
          "text": "",
          "name": "closeRows",
          "x": 15,
          "leftRadius": 4,
          "icon": "m0 0-3.6294 3.6294q-.0706.0706-.1206.0806-.05.01-.14-.08t-.095-.13q-.005-.04.0966-.1416l3.5466-3.5466q.1518-.1518.3428-.1518.1908 0 .349.15l3.55 3.55q.07.08.075.12t-.085.13q-.09.09-.13.09t-.1306-.0906l-3.6294-3.6094z"
        },
        {
          "side": "left",
          "text": "",
          "name": "openRows",
          "x": 45,
          "rightRadius": 4,
          "icon": "m0 6 3.63-3.65q.07-.07.135-.07t.135.07q.09.09.09.135t-.09.135l-3.55 3.55q-.08.07-.165.115t-.185.045q-.1 0-.185-.045t-.155-.115l-3.55-3.55q-.08-.07-.065-.135t.085-.135q.09-.09.13-.09t.14.09l3.6 3.65zm0-5.51 3.63-3.65q.07-.07.135-.07t.135.07q.09.09.09.135t-.09.135l-3.55 3.55q-.08.07-.165.115t-.185.045q-.1 0-.185-.045t-.155-.115l-3.55-3.55q-.08-.07-.065-.135t.085-.135q.09-.09.13-.09t.14.09l3.6 3.65z"
        },
        {
          "side": "left",
          "text": "",
          "name": "closeColumns",
          "x": 105,
          "leftRadius": 4,
          "icon": "m-1 1 3.63 3.63q.07.07.08.12t-.08.14q-.09.09-.13.095t-.1368-.1018l-3.5432-3.5432q-.08-.078-.12-.159-.04-.081-.04-.185t.04-.185q.04-.081.12-.161l3.5432-3.5432q.0768-.0768.1268-.0818t.14.085q.09.09.09.13t-.09.13l-3.63 3.63z"
        },
        {
          "side": "left",
          "text": "",
          "name": "openColumns",
          "x": 135,
          "rightRadius": 4,
          "icon": "m0 1-3.65-3.63q-.08-.07-.075-.135t.075-.135q.09-.09.13-.09t.14.09l3.55 3.55q.07.08.115.165t.045.185q0 .1-.045.185t-.115.155l-3.55 3.55q-.08.08-.135.065t-.115-.085q-.1-.09-.1-.13t.1-.14l3.63-3.6zm4.91 0-3.65-3.63q-.08-.07-.075-.135t.075-.135q.09-.09.13-.09t.14.09l3.55 3.55q.07.08.115.165t.045.185q0 .1-.045.185t-.115.155l-3.55 3.55q-.08.08-.135.065t-.115-.085q-.1-.09-.1-.13t.1-.14l3.63-3.6z"
        },
        {
          "side": "right",
          "text": "All",
          "name": "All",
          "x": 50,
          "rightRadius": 4
        },
        {
          "side": "right",
          "text": "Years",
          "name": "Years",
          "x": 100
        },
        {
          "side": "right",
          "text": "Months",
          "name": "Months",
          "x": 150
        },
        {
          "side": "right",
          "text": "Days",
          "name": "Days",
          "x": 200,
          "leftRadius": 4
        }
      ]
    }
  ],
  "marks": [
    {
      "name": "buttonMarks",
      "description": "All buttons",
      "type": "group",
      "from": {
        "data": "buttons"
      },
      "clip": {
        "signal": "!showButtons"
      },
      "encode": {
        "update": {
          "x": {
            "signal": "datum.side=='left'?datum.x:columnsWidth+ganttWidth-datum.x"
          },
          "width": {
            "signal": "datum.side=='left'?30:50"
          },
          "y": {
            "value": -60
          },
          "height": {
            "signal": "18"
          },
          "stroke": {
            "signal": "'#7f7f7f'"
          },
          "strokeWidth": {
            "value": 1
          },
          "cornerRadiusTopLeft": {
            "field": "leftRadius"
          },
          "cornerRadiusBottomLeft": {
            "field": "leftRadius"
          },
          "cornerRadiusTopRight": {
            "field": "rightRadius"
          },
          "cornerRadiusBottomRight": {
            "field": "rightRadius"
          },
          "cursor": {
            "value": "pointer"
          },
          "fill": [
            {
              "test": "indexof( hover,datum.name)>-1",
              "value": "#4e95d9"
            },
            {
              "test": "datum.name=='closeRows' && data('collapsedPhases').length == data('phases').length",
              "value": "#4e95d9"
            },
            {
              "test": "datum.name=='closeColumns' && !showColumns",
              "value": "#4e95d9"
            },
            {
              "test": "datum.name=='openColumns' && showColumns",
              "value": "#4e95d9"
            },
            {
              "test": "datum.name=='openRows' && data('collapsedPhases').length == 0",
              "value": "#4e95d9"
            },
            {
              "test": "datum.name=='Days' && dayBandwidthRound == minDayBandwidth",
              "value": "#4e95d9"
            },
            {
              "test": "datum.name=='Months' && dayBandwidthRound>=minYearBandwidth && dayBandwidthRound<minDayBandwidth",
              "value": "#4e95d9"
            },
            {
              "test": "datum.name=='Years' && dayBandwidthRound<minYearBandwidth",
              "value": "#4e95d9"
            },
            {
              "value": "white"
            }
          ]
        }
      },
      "marks": [
        {
          "name": "buttonText",
          "interactive": false,
          "type": "text",
          "encode": {
            "update": {
              "text": {
                "signal": "parent.text"
              },
              "baseline": {
                "value": "middle"
              },
              "align": {
                "value": "center"
              },
              "x": {
                "signal": "item.mark.group.width/2"
              },
              "y": {
                "signal": "10"
              },
              "fill": [
                {
                  "test": "indexof( hover,parent.text)>-1",
                  "value": "white"
                },
                {
                  "test": "parent.text=='closeRows' && data('collapsedPhases').length == data('phases').length",
                  "value": "white"
                },
                {
                  "test": "parent.text=='openRows' && data('collapsedPhases').length == 0",
                  "value": "white"
                },
                {
                  "test": "parent.text=='Days' && dayBandwidthRound == minDayBandwidth",
                  "value": "white"
                },
                {
                  "test": "parent.text=='Months' && dayBandwidthRound>=minYearBandwidth && dayBandwidthRound<minDayBandwidth",
                  "value": "white"
                },
                {
                  "test": "parent.text=='Years' && dayBandwidthRound<minYearBandwidth",
                  "value": "white"
                },
                {
                  "value": "#7f7f7f"
                }
              ]
            }
          }
        },
        {
          "name": "buttonIcons",
          "interactive": false,
          "type": "path",
          "encode": {
            "update": {
              "path": {
                "signal": "parent.icon"
              },
              "x": {
                "signal": "item.mark.group.width/2"
              },
              "y": {
                "signal": "8"
              },
              "stroke": [
                {
                  "test": "indexof( hover,parent.name)>-1",
                  "value": "white"
                },
                {
                  "test": "parent.name=='closeColumns' && !showColumns",
                  "value": "white"
                },
                {
                  "test": "parent.name=='openColumns' && showColumns",
                  "value": "white"
                },
                {
                  "test": "parent.name=='closeRows' && data('collapsedPhases').length == data('phases').length",
                  "value": "white"
                },
                {
                  "test": "parent.name=='openRows' && data('collapsedPhases').length == 0",
                  "value": "white"
                },
                {
                  "value": "#7f7f7f"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "xDomainText",
      "data": [
        {}
      ],
      "interactive": false,
      "type": "text",
      "encode": {
        "update": {
          "text": {
            "signal": "showDomainSpanLabel?timeFormat(xDom[0],'%m/%d/%y') +' - ' + timeFormat(xDom[1],'%m/%d/%y'):null"
          },
          "baseline": {
            "value": "bottom"
          },
          "align": {
            "value": "right"
          },
          "x": {
            "signal": "columnsWidth+ganttWidth-220"
          },
          "y": {
            "signal": "-45"
          },
          "fill": {
            "signal": "textColour"
          }
        }
      }
    },
    {
      "name": "phaseBackgrounds",
      "description": "Background rect for phases",
      "type": "rect",
      "clip": true,
      "zindex": 0,
      "from": {
        "data": "phases"
      },
      "encode": {
        "update": {
          "x": {
            "value": 0
          },
          "x2": {
            "signal": "columnsWidth"
          },
          "y": {
            "signal": "scale('y', datum.id)"
          },
          "height": {
            "signal": "bandwidth('y')"
          },
          "fill": {
            "value": "#dceaf7"
          },
          "opacity": {
            "value": 0.3
          }
        }
      }
    },
    {
      "name": "taskLabelSizes",
      "description": "Hidden label sizes to support tooltips when the task name doesn't completely fit",
      "type": "text",
      "clip": true,
      "from": {
        "data": "yScale"
      },
      "encode": {
        "enter": {
          "x": {
            "value": -100
          },
          "y": {
            "value": -100
          },
          "fill": {
            "value": "transparent"
          },
          "fontSize": {
            "value": 11
          },
          "text": {
            "signal": "datum.phase == datum.task?upper(datum.task):datum.task"
          },
          "font": {
            "signal": "datum.phase == datum.task?'Arial':'Segoe UI'"
          },
          "fontWeight": {
            "signal": "datum.phase == datum.task?'bold':'normal'"
          }
        }
      }
    },
    {
      "type": "rect",
      "name": "taskTooltips",
      "description": "Hidden rect to support tooltips when the task name doesn't completely fit",
      "from": {
        "data": "taskLabelSizes"
      },
      "clip": true,
      "zindex": 101,
      "encode": {
        "update": {
          "x": {
            "value": -15
          },
          "x2": {
            "signal": "taskColumnWidth"
          },
          "y": {
            "signal": "scale('y', datum.datum.id)"
          },
          "height": {
            "signal": "bandwidth('y')"
          },
          "fill": {
            "value": "transparent"
          },
          "tooltip": {
            "signal": "datum.bounds.x2 - datum.bounds.x1>=taskColumnWidth-columnPadding-16? datum.datum.task:null"
          },
          "cursor": {
            "signal": "datum.datum.phase == datum.datum.task?'pointer':'auto'"
          },
          "href": {
            "field": "datum.hyperlink"
          }
        }
      }
    },
    {
      "type": "group",
      "name": "columnHolder",
      "style": "cell",
      "layout": {
        "bounds": "flush",
        "align": "each"
      },
      "encode": {
        "update": {
          "x": {
            "signal": "0"
          },
          "stroke": {
            "value": "transparent"
          },
          "width": {
            "signal": "columnsWidth"
          },
          "height": {
            "signal": "height"
          }
        }
      },
      "marks": [
        {
          "type": "group",
          "name": "taskColumnWidth",
          "title": {
            "text": "Task",
            "anchor": "start",
            "frame": "group",
            "align": "left",
            "dx": 16
          },
          "encode": {
            "enter": {
              "stroke": {
                "value": ""
              },
              "width": {
                "signal": "taskColumnWidth"
              },
              "height": {
                "signal": "height"
              }
            }
          },
          "marks": [
            {
              "type": "text",
              "style": "col",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "align": {
                    "value": "left"
                  },
                  "dx": {
                    "value": 16
                  },
                  "y": {
                    "signal": "scale('y', datum.id)+bandwidth('y')/2"
                  },
                  "text": {
                    "signal": "datum.phase == datum.task?upper(datum.task):datum.task"
                  },
                  "font": {
                    "signal": "datum.phase == datum.task?'Arial':'Segoe UI'"
                  },
                  "fontWeight": {
                    "signal": "datum.phase == datum.task?'bold':'normal'"
                  },
                  "limit": {
                    "signal": "taskColumnWidth-16-columnPadding"
                  },
                  "fill": {
                    "signal": "datum.id == itemHovered.id  ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):textColour"
                  }
                }
              }
            },
            {
              "type": "symbol",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "fill": {
                    "signal": "datum.id == itemHovered.id && datum.phase == datum.task ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):datum.phase == datum.task ?scale('cDark', datum.phase):'transparent'"
                  },
                  "x": {
                    "signal": "sqrt(90)/2"
                  },
                  "size": {
                    "value": 90
                  },
                  "yc": {
                    "signal": "(scale('y', datum.id)+bandwidth('y')/2)-1"
                  },
                  "shape": {
                    "signal": "datum.phase == datum.task && !indata('collapsedPhases', 'phase', datum.phase)?'triangle-down':datum.phase == datum.task && indata('collapsedPhases', 'phase', datum.phase)?'triangle-right':''"
                  }
                }
              }
            }
          ]
        },
        {
          "type": "group",
          "name": "startColumnWidth",
          "clip": {
            "signal": "!showColumns"
          },
          "title": {
            "text": {
              "signal": "'Start'"
            },
            "anchor": "middle",
            "dx": {
              "signal": "-(columnPadding/2)"
            },
            "frame": "group",
            "align": "center"
          },
          "encode": {
            "update": {
              "width": {
                "signal": "startColumnWidth"
              },
              "height": {
                "signal": "height"
              },
              "stroke": {
                "value": ""
              }
            }
          },
          "marks": [
            {
              "type": "text",
              "style": "col",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "align": {
                    "value": "center"
                  },
                  "x": {
                    "signal": "(startColumnWidth-columnPadding)/2"
                  },
                  "y": {
                    "signal": "scale('y', datum.id)+bandwidth('y')/2"
                  },
                  "text": {
                    "signal": "timeFormat(datum.start,' %m/%d/%y')"
                  },
                  "font": {
                    "signal": "datum.phase == datum.task?'Arial':'Segoe UI'"
                  },
                  "fontWeight": {
                    "signal": "datum.phase == datum.task?'bold':'normal'"
                  },
                  "fill": {
                    "signal": "datum.id == itemHovered.id  ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):textColour"
                  }
                }
              }
            }
          ]
        },
        {
          "type": "group",
          "name": "endColumnWidth",
          "clip": {
            "signal": "!showColumns"
          },
          "title": {
            "text": {
              "signal": "'End'"
            },
            "anchor": "middle",
            "dx": {
              "signal": "-(columnPadding/2)"
            },
            "frame": "group",
            "align": "center"
          },
          "encode": {
            "update": {
              "width": {
                "signal": "endColumnWidth"
              },
              "stroke": {
                "value": "transparent"
              },
              "height": {
                "signal": "height"
              }
            }
          },
          "marks": [
            {
              "type": "text",
              "style": "col",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "align": {
                    "value": "center"
                  },
                  "x": {
                    "signal": "(startColumnWidth-columnPadding)/2"
                  },
                  "y": {
                    "signal": "scale('y', datum.id)+bandwidth('y')/2"
                  },
                  "text": {
                    "signal": "timeFormat(datum.labelEnd,' %m/%d/%y')"
                  },
                  "font": {
                    "signal": "datum.phase == datum.task?'Arial':'Segoe UI'"
                  },
                  "fontWeight": {
                    "signal": "datum.phase == datum.task?'bold':'normal'"
                  },
                  "fill": {
                    "signal": "datum.id == itemHovered.id  ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):textColour"
                  }
                }
              }
            }
          ]
        },
        {
          "type": "group",
          "name": "daysColumnWidth",
          "clip": {
            "signal": "!showColumns"
          },
          "title": {
            "text": {
              "signal": "'Days'"
            },
            "anchor": "end",
            "dx": {
              "signal": "-columnPadding"
            },
            "frame": "group",
            "align": "right"
          },
          "encode": {
            "update": {
              "width": {
                "signal": "daysColumnWidth"
              },
              "stroke": {
                "value": "transparent"
              },
              "height": {
                "signal": "height"
              }
            }
          },
          "marks": [
            {
              "type": "text",
              "style": "col",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "align": {
                    "value": "right"
                  },
                  "x": {
                    "signal": "daysColumnWidth-columnPadding"
                  },
                  "y": {
                    "signal": "scale('y', datum.id)+bandwidth('y')/2"
                  },
                  "text": {
                    "signal": "format(datum.days,',')+' d'"
                  },
                  "fontWeight": {
                    "signal": "datum.phase == datum.task?'bold':'normal'"
                  },
                  "font": {
                    "signal": "datum.phase == datum.task?'Arial':'Segoe UI'"
                  },
                  "fill": {
                    "signal": "datum.id == itemHovered.id  ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):textColour"
                  }
                }
              }
            }
          ]
        },
        {
          "type": "group",
          "name": "statusColumn",
          "clip": {
            "signal": "!showColumns"
          },
          "title": {
            "text": {
              "signal": "statusColumnPresent?'Status':''"
            },
            "anchor": "middle",
            "frame": "group",
            "align": "center"
          },
          "encode": {
            "update": {
              "width": {
                "signal": "statusColumnWidth"
              },
              "stroke": {
                "value": "transparent"
              },
              "height": {
                "signal": "height"
              }
            }
          },
          "marks": [
            {
              "type": "rect",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "x": {
                    "signal": "statusColumnWidth/2 - 29"
                  },
                  "width": {
                    "signal": "57"
                  },
                  "height": {
                    "value": 20
                  },
                  "fill": {
                    "signal": "datum.id == itemHovered.id && isValid(datum.status) && datum.status!='' ?merge(hsl(scale('statusBackground',datum.status)), {l:0.72}):scale('statusBackground',datum.status)"
                  },
                  "yc": {
                    "signal": "(scale('y',datum.id)+bandwidth('y')/2)"
                  }
                }
              }
            },
            {
              "type": "text",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "baseline": {
                    "value": "middle"
                  },
                  "align": {
                    "value": "center"
                  },
                  "x": {
                    "signal": "statusColumnWidth/2"
                  },
                  "fill": {
                    "signal": "datum.id == itemHovered.id && isValid(datum.status) && datum.status!='' ?merge(hsl(scale('statusFont',datum.status)), {l:0.20}):scale('statusFont',datum.status)"
                  },
                  "y": {
                    "signal": "(scale('y',datum.id)+bandwidth('y')/2)+1"
                  },
                  "text": {
                    "signal": "scale('statusText',datum.status)"
                  }
                }
              }
            }
          ]
        },
        {
          "type": "group",
          "name": "completionColumn",
          "clip": {
            "signal": "!showColumns"
          },
          "title": {
            "text": {
              "signal": "'Progress'"
            },
            "anchor": "start",
            "frame": "group",
            "align": "left"
          },
          "encode": {
            "update": {
              "width": {
                "signal": "progressColumnWidth"
              },
              "stroke": {
                "value": "transparent"
              },
              "height": {
                "signal": "height"
              }
            }
          },
          "marks": [
            {
              "type": "rect",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "x": {
                    "signal": "1"
                  },
                  "width": {
                    "signal": "item.mark.group.width-2-columnPadding"
                  },
                  "stroke": {
                    "signal": "'#a0d786'"
                  },
                  "yc": {
                    "signal": "(scale('y',datum.id)+bandwidth('y')/2)"
                  },
                  "fill": {
                    "value": "white"
                  },
                  "height": {
                    "signal": "bandwidth('y')-yPaddingInner*1"
                  },
                  "strokeWidth": {
                    "signal": "datum.id == itemHovered.id?2:1"
                  }
                }
              }
            },
            {
              "type": "rect",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "x": {
                    "signal": "1"
                  },
                  "width": {
                    "signal": "((item.mark.group.width-columnPadding)/100)*datum.completion"
                  },
                  "fill": {
                    "signal": "'#00B050'"
                  },
                  "yc": {
                    "signal": "(scale('y',datum.id)+bandwidth('y')/2)"
                  },
                  "strokeWidth": {
                    "value": 1
                  },
                  "height": {
                    "signal": "bandwidth('y')-yPaddingInner*1"
                  }
                }
              }
            },
            {
              "type": "text",
              "clip": true,
              "from": {
                "data": "yScale"
              },
              "encode": {
                "update": {
                  "align": {
                    "value": "left"
                  },
                  "dx": {
                    "value": 3
                  },
                  "fill": {
                    "signal": "textColour"
                  },
                  "y": {
                    "signal": "scale('y',datum.id)+bandwidth('y')/2"
                  },
                  "text": {
                    "signal": "datum.completion+'%'"
                  }
                }
              }
            }
          ]
        }
      ]
    },
    {
      "type": "group",
      "name": "weekendContainer",
      "encode": {
        "update": {
          "x": {
            "signal": "columnsWidth"
          },
          "y": {
            "signal": "-15"
          },
          "clip": {
            "signal": "true"
          },
          "height": {
            "signal": "height+15"
          },
          "width": {
            "signal": "ganttWidth"
          },
          "fill": {
            "value": "transparent"
          }
        }
      },
      "marks": [
        {
          "type": "rect",
          "description": "Weekend shading",
          "name": "weekendShading",
          "from": {
            "data": "weekends"
          },
          "encode": {
            "update": {
              "x": {
                "signal": "scale('x',datum.start)"
              },
              "x2": {
                "signal": "scale('x',datum.end)"
              },
              "y": {
                "signal": "dayBandwidthRound>=minDayBandwidth?0:15"
              },
              "y2": {
                "signal": "scaledHeight<height?yRange[1]+15:height+15"
              },
              "strokeWidth": {
                "signal": "1"
              },
              "stroke": {
                "value": "#f1f1f1"
              },
              "fill": {
                "value": "#f1f1f1"
              }
            }
          }
        },
        {
          "name": "todayHighlight",
          "description": "Today highlight",
          "type": "rect",
          "data": [
            {}
          ],
          "encode": {
            "update": {
              "x": {
                "signal": "scale('x',today) "
              },
              "width": {
                "signal": "dayBandwidth"
              },
              "y": {
                "value": 0
              },
              "height": {
                "signal": "15"
              },
              "fill": {
                "value": "#FF0000"
              }
            }
          }
        }
      ]
    },
    {
      "type": "group",
      "name": "ganttContainer",
      "encode": {
        "update": {
          "x": {
            "signal": "columnsWidth"
          },
          "y": {
            "signal": "0"
          },
          "clip": {
            "signal": "true"
          },
          "height": {
            "signal": "height"
          },
          "width": {
            "signal": "ganttWidth"
          },
          "fill": {
            "value": "transparent"
          }
        }
      },
      "marks": [
        {
          "name": "completionLabelSizes",
          "type": "text",
          "from": {
            "data": "tasks"
          },
          "encode": {
            "enter": {
              "fill": {
                "value": "transparent"
              },
              "text": {
                "signal": "datum.completionLabel"
              }
            }
          }
        },
        {
          "name": "taskLabels",
          "description": "Task, milestone and phase names",
          "from": {
            "data": "yScale"
          },
          "type": "text",
          "encode": {
            "update": {
              "x": {
                "scale": "x",
                "field": "end"
              },
              "align": {
                "signal": "'left'"
              },
              "dx": {
                "signal": "datum.milestone?sqrt(milestoneSymbolSize)/2 - dayBandwidth/2 + 5:5"
              },
              "y": {
                "signal": "datum.phase == datum.task?scale('y', datum.id)-2:scale('y', datum.id)"
              },
              "dy": {
                "signal": "bandwidth('y')/2"
              },
              "fill": {
                "signal": "datum.id == itemHovered.id  ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):textColour"
              },
              "text": {
                "signal": "datum.milestone?datum.task:datum.task && datum.assignee? datum.task + ' ('+ datum.days+' d'+')' +' - ' + datum.assignee:datum.task + ' ('+ datum.days+' d'+')'"
              }
            }
          }
        },
        {
          "type": "group",
          "from": {
            "facet": {
              "name": "dependencyLinesFacet",
              "data": "dependencyLines",
              "groupby": [
                "id",
                "sourceId"
              ]
            }
          },
          "marks": [
            {
              "type": "line",
              "from": {
                "data": "dependencyLinesFacet"
              },
              "encode": {
                "enter": {
                  "x": {
                    "signal": "datum.value[0]"
                  },
                  "y": {
                    "signal": "datum.value[1]"
                  },
                  "stroke": {
                    "value": "#888888"
                  },
                  "strokeWidth": {
                    "value": 1
                  },
                  "interpolate": {
                    "value": "linear"
                  },
                  "strokeJoin": {
                    "value": "bevel"
                  },
                  "strokeCap": {
                    "value": "round"
                  },
                  "defined": {
                    "value": true
                  }
                },
                "update": {
                  "stroke": {
                    "signal": "datum.id == itemHovered.id  ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):'#888888'"
                  },
                  "strokeWidth": {
                    "signal": "datum.id == itemHovered.id?1.5:1"
                  }
                }
              }
            }
          ]
        },
        {
          "name": "todayRule",
          "description": "Today rule",
          "type": "rule",
          "data": [
            {}
          ],
          "encode": {
            "update": {
              "x": {
                "signal": "scale('x',today+oneDay/2) "
              },
              "y2": {
                "signal": "scaledHeight<height?yRange[1]:height"
              },
              "strokeWidth": {
                "value": 1
              },
              "stroke": {
                "value": "#FF0000"
              },
              "strokeDash": {
                "value": [
                  2,
                  2
                ]
              },
              "opacity": {
                "value": 0.8
              }
            }
          }
        },
        {
          "name": "todayText",
          "description": "Today text",
          "type": "text",
          "data": [
            {}
          ],
          "encode": {
            "update": {
              "x": {
                "signal": "scale('x',today+oneDay/2)"
              },
              "fill": {
                "value": "#FF0000"
              },
              "text": {
                "value": ""
              },
              "angle": {
                "signal": "90"
              },
              "baseline": {
                "value": "middle"
              },
              "dx": {
                "value": 10
              },
              "dy": {
                "value": 10
              },
              "opacity": {
                "value": 0.7
              }
            }
          }
        },
        {
          "name": "taskBars",
          "description": "The task bars (serve as an outline for percent complete)",
          "type": "group",
          "from": {
            "data": "tasks"
          },
          "encode": {
            "update": {
              "clip": {
                "signal": "true"
              },
              "x": {
                "scale": "x",
                "field": "start"
              },
              "x2": {
                "scale": "x",
                "field": "end"
              },
              "yc": {
                "signal": "(scale('y',datum.id)+bandwidth('y')/2)"
              },
              "height": {
                "signal": "bandwidth('y')-yPaddingInner*2"
              },
              "tooltip": {
                "signal": "showTooltips&&down==null?{'Phase':datum.phase ,'Task':datum.task , 'Start':timeFormat(datum.start,'%a, %d %B %Y' ),'End':timeFormat(datum.labelEnd,'%a, %d %B %Y' ), 'Days':datum.days, 'Assignee':datum.assignee ,'Progress':datum.completionLabel }:null"
              },
              "fill": {
                "signal": "datum.id == itemHovered.id  || indexof(itemHovered.dependencies,datum.id )> -1 ?merge(hsl(scale('cLight', datum.phase)), {l:0.65}):scale('cLight', datum.phase)"
              },
              "stroke": {
                "signal": "datum.id == itemHovered.id  || indexof(itemHovered.dependencies,datum.id )> -1 ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):scale('cDark', datum.phase)"
              },
              "cornerRadius": {
                "value": 5
              },
              "zindex": {
                "value": 101
              },
              "strokeWidth": {
                "signal": "datum.id == itemHovered.id  || indexof(itemHovered.dependencies,datum.id )> -1 ?1.5:1"
              },
              "href": {
                "field": "hyperlink"
              }
            }
          },
          "transform": [
            {
              "type": "lookup",
              "from": "completionLabelSizes",
              "key": "datum.id",
              "fields": [
                "datum.id"
              ],
              "values": [
                "bounds.x1",
                "bounds.x2"
              ],
              "as": [
                "a",
                "b"
              ]
            }
          ],
          "marks": [
            {
              "name": "taskFills",
              "description": "Percent complete for each task",
              "type": "rect",
              "interactive": false,
              "encode": {
                "update": {
                  "x": {
                    "signal": "0"
                  },
                  "y": {
                    "signal": "0"
                  },
                  "height": {
                    "signal": "item.mark.group.height"
                  },
                  "width": {
                    "signal": "(item.mark.group.width/100)* item.mark.group.datum.completion"
                  },
                  "fill": {
                    "signal": "toString(item.mark.group.datum.id) == itemHovered.id  || indexof(itemHovered.dependencies,toString(item.mark.group.datum.id) )> -1 ?merge(hsl(scale('cDark', item.mark.group.datum.phase)), {l:0.40}):scale('cDark', item.mark.group.datum.phase)"
                  },
                  "strokeWidth": {
                    "value": 0
                  },
                  "cornerRadiusBottomLeft": {
                    "value": 5
                  },
                  "cornerRadiusTopLeft": {
                    "value": 5
                  }
                }
              }
            },
            {
              "name": "completeText",
              "description": "Completion Text",
              "type": "text",
              "interactive": false,
              "encode": {
                "update": {
                  "x": {
                    "signal": "(item.mark.group.width/100)* parent.completion"
                  },
                  "align": {
                    "signal": "'right'"
                  },
                  "dx": {
                    "signal": "-2"
                  },
                  "baseline": {
                    "value": "middle"
                  },
                  "y": {
                    "signal": "item.mark.group.height/2+1"
                  },
                  "text": {
                    "signal": "round(((item.mark.group.width/100)* parent.completion))>=(item.mark.group.b+4) && parent.completion>0?parent.completionLabel:''"
                  },
                  "fill": {
                    "signal": "luminance(item.mark.group.stroke) >=0.45?'black':'white'"
                  }
                }
              }
            }
          ]
        },
        {
          "name": "phaseOutline",
          "description": "The phase bar outlines",
          "type": "path",
          "from": {
            "data": "phasePaths"
          },
          "encode": {
            "update": {
              "path": {
                "signal": "datum.phasePath"
              },
              "fill": {
                "signal": "datum.id == itemHovered.id  || indexof(itemHovered.dependencies,datum.id )> -1 ?merge(hsl(scale('cLight', datum.phase)), {l:0.65}):scale('cLight', datum.phase)"
              },
              "stroke": {
                "signal": "datum.id == itemHovered.id  || indexof(itemHovered.dependencies,datum.id )> -1 ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):scale('cDark', datum.phase)"
              },
              "strokeWidth": {
                "signal": "datum.id == itemHovered.id?1.5:1"
              },
              "tooltip": {
                "signal": "showTooltips&&down==null?{'Phase':datum.phase , 'Start':timeFormat(datum.start,'%a, %d %B %Y' ),'End':timeFormat(datum.labelEnd,'%a, %d %B %Y' ), 'Days':datum.days,'Progress':datum.completion+'%' }:null"
              },
              "cursor": {
                "value": "pointer"
              }
            }
          }
        },
        {
          "name": "phaseGroup",
          "description": "Group to hold the x y coordinates for the SVG clipping fills",
          "type": "group",
          "clip": true,
          "from": {
            "data": "phasePaths"
          },
          "encode": {
            "update": {
              "strokeWidth": {
                "value": 0
              },
              "stroke": {
                "value": "red"
              },
              "x": {
                "scale": "x",
                "field": "start",
                "offset": 0
              },
              "x2": {
                "scale": "x",
                "field": "end",
                "offset": 0
              },
              "yc": {
                "signal": "scale('y',datum.id)+bandwidth('y')/2"
              },
              "height": {
                "signal": "bandwidth('y')-yPaddingInner*2"
              }
            }
          },
          "marks": [
            {
              "name": "phaseFills",
              "description": "Percent complete for each phase. Clipping path signal has to be here as it fails to update on zoom when coming from a dataset. The only value available in the clipping path signal is parent!",
              "type": "rect",
              "interactive": false,
              "clip": {
                "path": {
                  "signal": "'M 0 0'  + ' L ' +   (scale('x', parent.end) - scale('x', parent.start)) +' 0'    + ' v ' +  phaseSymbolHeight + ' L ' + (scale('x', parent.end) - scale('x', parent.start) - phaseSymbolWidth) +' '  +   (phaseSymbolHeight/2) + ' H ' +  phaseSymbolWidth + ' L 0 '   +   phaseSymbolHeight + ' z'"
                }
              },
              "encode": {
                "update": {
                  "height": {
                    "signal": "phaseSymbolHeight"
                  },
                  "width": {
                    "signal": "(item.mark.group.width/100)* item.mark.group.datum.completion"
                  },
                  "fill": {
                    "signal": "toString(item.mark.group.datum.id) == itemHovered.id  || indexof(itemHovered.dependencies,toString(item.mark.group.datum.id) )> -1 ?merge(hsl(scale('cDark', item.mark.group.datum.phase)), {l:0.40}):scale('cDark', item.mark.group.datum.phase)"
                  },
                  "strokeWidth": {
                    "value": 0
                  },
                  "stroke": {
                    "value": "red"
                  }
                }
              }
            }
          ]
        },
        {
          "name": "milestoneSymbols",
          "description": "Milestones",
          "type": "symbol",
          "from": {
            "data": "milestones"
          },
          "encode": {
            "update": {
              "x": {
                "signal": "scale('x',datum.start)+dayBandwidth/2"
              },
              "y": {
                "signal": "scale('y', datum.id)+bandwidth('y')/2"
              },
              "size": {
                "signal": "milestoneSymbolSize"
              },
              "shape": {
                "value": "diamond"
              },
              "fill": {
                "signal": "(datum.id == itemHovered.id  || indexof(itemHovered.dependencies,datum.id )> -1)  && datum.completion > 0 ?merge(hsl(scale( 'cDark', datum.phase)), {l:0.40}):datum.id == itemHovered.id  || indexof(itemHovered.dependencies,datum.id )> -1   ?merge(hsl(scale( 'cLight', datum.phase)), {l:0.65}):datum.completion > 0?  scale('cDark', datum.phase):scale('cLight', datum.phase)"
              },
              "stroke": {
                "signal": "datum.id == itemHovered.id  || indexof(itemHovered.dependencies,datum.id )> -1 ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):scale('cDark', datum.phase)"
              },
              "strokeWidth": {
                "signal": "datum.id == itemHovered.id  || indexof(itemHovered.dependencies,datum.id )> -1  ?1.5:1"
              },
              "tooltip": {
                "signal": "showTooltips&&down==null?{'Phase':datum.phase ,'Task':datum.task , 'Start':timeFormat(datum.start,'%a, %d %B %Y' ),'End':timeFormat(datum.labelEnd,'%a, %d %B %Y' ), 'Days':datum.days,'Assignee':datum.assignee ,'Progress':datum.completionLabel }:null"
              }
            }
          }
        },
        {
          "name": "taskDependencyArrowsymbol",
          "description": "Dependency arrows",
          "type": "symbol",
          "from": {
            "data": "dependencyArrows"
          },
          "encode": {
            "update": {
              "shape": {
                "value": "triangle-right"
              },
              "x": {
                "signal": "scale('x',datum.start)",
                "offset": {
                  "signal": "datum.milestone!=null && datum.milestone != false? -(sqrt(arrowSymbolSize))/2 + dayBandwidth/2 - (sqrt(milestoneSymbolSize))/2 +1:-(sqrt(arrowSymbolSize)/2) +1"
                }
              },
              "y": {
                "signal": "scale('y', datum.id)+bandwidth('y')/2"
              },
              "fill": {
                "signal": "datum.id == itemHovered.id  ?merge(hsl(scale('cDark', datum.phase)), {l:0.40}):'#6a6a6a'"
              },
              "size": {
                "signal": "arrowSymbolSize"
              }
            }
          }
        }
      ]
    },
    {
      "name": "taskSelector",
      "description": "Hidden rect to support phase expand and collapse",
      "type": "rect",
      "clip": true,
      "zindex": 99,
      "from": {
        "data": "yScale"
      },
      "encode": {
        "update": {
          "x": {
            "value": -15
          },
          "x2": {
            "signal": "columnsWidth"
          },
          "y": {
            "signal": "scale('y', datum.id)"
          },
          "height": {
            "signal": "bandwidth('y')"
          },
          "fill": {
            "value": "transparent"
          },
          "cursor": {
            "signal": "datum.phase == datum.task?'pointer':'auto'"
          },
          "href": {
            "field": "hyperlink"
          }
        }
      }
    },
    {
      "type": "group",
      "name": "axisClipper",
      "clip": true,
      "encode": {
        "update": {
          "width": {
            "signal": "columnsWidth"
          },
          "stroke": {
            "value": "transparent"
          },
          "height": {
            "signal": "height"
          }
        }
      },
      "axes": [
        {
          "scale": "y",
          "orient": "right",
          "encode": {
            "ticks": {
              "update": {
                "x2": {
                  "signal": "-columnsWidth"
                }
              }
            }
          },
          "tickColor": "#f1f1f1",
          "labels": false,
          "title": "",
          "grid": false,
          "ticks": true,
          "bandPosition": {
            "signal": "0"
          }
        }
      ]
    }
  ],
  "axes": [
    {
      "description": "Bottom date axis",
      "ticks": true,
      "labelPadding": -12,
      "scale": "x",
      "position": {
        "signal": "columnsWidth"
      },
      "orient": "top",
      "tickSize": 15,
      "grid": false,
      "zindex": 1,
      "labelOverlap": false,
      "formatType": "time",
      "tickCount": {
        "signal": "dayBandwidthRound>=minYearBandwidth?'day':'month'"
      },
      "encode": {
        "ticks": {
          "update": {
            "strokeWidth": [
              {
                "test": "dayBandwidthRound>=minDayBandwidth",
                "value": 1
              },
              {
                "test": "dayBandwidthRound>=minMonthBandwidth && dayBandwidthRound<minDayBandwidth && date(datum.value) == 1",
                "value": 1
              },
              {
                "test": "dayBandwidthRound>=minYearBandwidth && dayBandwidthRound<minMonthBandwidth && date(datum.value) == 1",
                "value": 1
              },
              {
                "test": "dayBandwidthRound<minYearBandwidth && dayofyear(datum.value) == 1",
                "value": 1
              },
              {
                "value": 0
              }
            ]
          }
        },
        "labels": {
          "update": {
            "text": [
              {
                "test": "dayBandwidthRound>=minDayBandwidth",
                "signal": "timeFormat(datum.value,'%d')"
              },
              {
                "test": "dayBandwidthRound>=minMonthBandwidth && dayBandwidthRound<minDayBandwidth && date(datum.value) == 15",
                "signal": "timeFormat(datum.value,'%B %y')"
              },
              {
                "test": "dayBandwidthRound>=minYearBandwidth && dayBandwidthRound<minMonthBandwidth && date(datum.value) == 15",
                "signal": "timeFormat(datum.value,'%b')"
              },
              {
                "test": "dayBandwidthRound<minYearBandwidth && month(datum.value) == 6",
                "signal": "timeFormat(datum.value,'%Y')"
              },
              {
                "value": ""
              }
            ],
            "dx": {
              "signal": "dayBandwidthRound/2"
            }
          }
        }
      }
    },
    {
      "description": "Top date axis",
      "scale": "x",
      "position": {
        "signal": "columnsWidth"
      },
      "domain": false,
      "orient": "top",
      "offset": 0,
      "tickSize": 22,
      "labelBaseline": "middle",
      "grid": false,
      "zindex": 0,
      "tickCount": {
        "signal": "dayBandwidthRound>=minYearBandwidth?'day':'month'"
      },
      "encode": {
        "ticks": {
          "update": {
            "strokeWidth": [
              {
                "test": "dayBandwidthRound>=minDayBandwidth && date(datum.value) == 1",
                "value": 1
              },
              {
                "test": "dayBandwidthRound>=minYearBandwidth && dayBandwidthRound<minDayBandwidth && dayofyear(datum.value) == 1",
                "value": 1
              },
              {
                "value": 0
              }
            ]
          }
        },
        "labels": {
          "update": {
            "text": [
              {
                "test": "dayBandwidthRound>=minDayBandwidth && date(datum.value) == 15",
                "signal": "timeFormat(datum.value,'%B %y')"
              },
              {
                "test": "dayBandwidthRound>=minYearBandwidth && dayBandwidthRound<minMonthBandwidth && month(datum.value) == 5 && date(datum.value) == 15",
                "signal": "timeFormat(datum.value,'%Y')"
              },
              {
                "value": ""
              }
            ],
            "dx": {
              "signal": "dayBandwidthRound/2"
            }
          }
        }
      }
    },
    {
      "description": "Month grid lines",
      "scale": "x",
      "position": {
        "signal": "columnsWidth"
      },
      "domain": false,
      "orient": "top",
      "labels": false,
      "grid": true,
      "tickSize": 0,
      "zindex": 0,
      "tickCount": {
        "signal": " dayBandwidthRound>=minMonthBandwidth || dayBandwidthRound<=0.35?0:'month'"
      }
    }
  ],
  "scales": [
    {
      "name": "x",
      "type": "time",
      "domain": {
        "signal": "xDom"
      },
      "range": {
        "signal": "[0,ganttWidth]"
      }
    },
    {
      "name": "y",
      "type": "band",
      "domain": {
        "fields": [
          {
            "data": "yScale",
            "field": "id"
          }
        ],
        "sort": {
          "op": "min",
          "field": "finalSort",
          "order": "ascending"
        }
      },
      "range": {
        "signal": "yRange"
      }
    },
    {
      "name": "cDark",
      "type": "ordinal",
      "range": {
        "signal": "colours.dark"
      },
      "domain": {
        "data": "input",
        "field": "phase"
      }
    },
    {
      "name": "cLight",
      "type": "ordinal",
      "range": {
        "signal": "colours.light"
      },
      "domain": {
        "data": "input",
        "field": "phase"
      }
    },
    {
      "name": "statusText",
      "type": "ordinal",
      "range": {
        "signal": "statusColumn.range"
      },
      "domain": {
        "signal": "statusColumn.domain"
      }
    },
    {
      "name": "statusFont",
      "type": "ordinal",
      "range": {
        "signal": "statusColumn.font"
      },
      "domain": {
        "signal": "statusColumn.domain"
      }
    },
    {
      "name": "statusBackground",
      "type": "ordinal",
      "range": {
        "signal": "statusColumn.background"
      },
      "domain": {
        "signal": "statusColumn.domain"
      }
    }
  ],
  "config": {
    "view": {
      "stroke": "transparent"
    },
    "style": {
      "col": {
        "fontSize": 11
      },
      "cell": {
        "strokeWidth": {
          "value": "0"
        }
      }
    },
    "font": "Segoe UI",
    "text": {
      "font": "Segoe UI",
      "fontSize": 10,
      "baseline": "middle"
    },
    "axis": {
      "labelColor": {
        "signal": "textColour"
      },
      "labelFontSize": 10
    },
    "title": {
      "color": {
        "signal": "textColour"
      }
    }
  }
}