Skip to content

Instantly share code, notes, and snippets.

@Giammaria
Last active October 8, 2025 14:27
Show Gist options
  • Save Giammaria/44697bc4d80fee19dbb672c8bc5743c1 to your computer and use it in GitHub Desktop.
Save Giammaria/44697bc4d80fee19dbb672c8bc5743c1 to your computer and use it in GitHub Desktop.
20250409_hierarchical_gantt_v_v2.2
{
"$schema": "https://vega.github.io/schema/vega/v6.json",
"bounds": "flush",
"autosize": {"type": "pad", "contains": "padding"},
"background": "#fff",
"signals": [
{"name": "isPowerBIVisual", "value": false},
{"name": "desiredHeight", "update": "300"},
{"name": "desiredWidth", "update": "1300"},
{
"name": "configColumn",
"description": "configurations for the columns that appear to the left of the Gantt",
"init": "{xOffset: 5, innerPadding: 25}"
},
{
"name": "configGantt",
"description": "configurations for the Gantt",
"update": "{x: ganttDimensions.width, childRect: {cornerRadius: 1, percentOfRowHeight: 0.5}, parentRect: {percentOfRowHeight: 0.25}, label: {xOffset: 7.5, font: 'Segoe UI', fontSize: 11, align: 'left', fontWeight: 400, fill: '#999'}, todayLine: {label: {text: 'Today'}, stroke: '#708090', strokeWidth: 0.5, strokeDash: [2,3] }}"
},
{"name": "configBorders", "init": "{'stroke': '#ccc', strokeWidth: 1.5}"},
{
"name": "configGanttGridlines",
"init": "{'topAxis': {'stroke': '#666', strokeWidth: 1.5}, secondaryAxis: {'stroke': '#ddd', strokeWidth: 1.5}}"
},
{
"name": "configAnimationDuration",
"init": "{granularityReset: 750, granularitySliderClick: 250, anchorReset: 750, anchorMapClick: 250, showDetails: 750, nodeExpandCollapse: 2500}"
},
{
"name": "configIncludedLeftHandColumns",
"description": "array of column titles to be included. 'name' must be included. 'startDate', 'endDate', 'duration', and 'progress' are all 'optional'",
"value": ["name", "startDate", "endDate", "test", "duration", "progress"]
},
{
"name": "configButtons",
"description": "configurations for the button controls",
"init": "{padding: 5, yOffset: 5, label: {text: 'Expand/Collapse All', font: 'Segoe UI', fontSize: 12, fill: '#888', fontStyle: 'regular', dy: 15}, outerStroke: '#777', fill: '#EEE', hoverFill: 'steelblue'}"
},
{
"name": "showDetailsConfig",
"description": "configurations for the show details toggle control",
"update": "{enabled: true, initialValue: true, xOffset: 40, track: {height: 7.5, width: 25, cornerRadius: 5, fill: '#EEE', stroke: '#777', strokeWidth: 1}, handle: {stroke: '#777', strokeWidth: 1, fill: '#fff'}, label: {text: 'Show Details', font: 'Segoe UI', fontSize: 12, fill: '#888', fontStyle: 'regular', dx: 10}, on: {fill: '#d1e0ec', fillOpacity: 1, stroke: '#777', strokeWidth: 1}, tooltip: {text: 'Show/hide detail columns'}}"
},
{
"name": "configTogglepanAndZoomMode",
"description": "configurations for the mouse wheel granularity control",
"update": "{enabled: true, initialValue: false, xOffset: 0, track: {height: 7.5, width: 25, cornerRadius: 5, fill: '#d1e0ec', stroke: '#777', strokeWidth: 1}, handle: {stroke: '#777', strokeWidth: 1, fill: '#fff'}, label: {text: 'Mouse wheel granularity', font: 'Segoe UI', fontSize: 12, fill: '#888', fontStyle: 'regular', dx: 5}, on: {fill: '#d1e0ec', fillOpacity: 1, stroke: '#777', strokeWidth: 1}, tooltip: {object: {title: 'Mouse Wheel Scroll vs. Zoom', '‎‎':'‎', '• ‎ ‎ ‎ ‎ ‎ ‎': 'Click here to toggle between vertical scrolling and date granularity zooming.', '‎‎‎':'‎', '• ‎ ‎ ‎ ‎ ‎ ‎‎': 'Alternatively, press the space bar to quickly toggle between modes.'}}}"
},
{
"name": "configDateStepSlider",
"description": "configurations for the date granularity slider control",
"update": "{enabled: true, innerPadding: 12.5, track: {height: 7.5, width: 130, cornerRadius: 5, fill: '#EEE', stroke: '#777', strokeWidth: 1}, handle: {outerStroke: '#777', outerStrokeWidth: 1, outerfill: '#fff', innerStroke: '#777', innerStrokeWidth: 1, innerFill: '#666'}, label: {text: 'Date Granularity', font: 'Segoe UI', fontSize: 10, fill: '#666', fontStyle: 'regular', dy: 10}, progress: {fill: '#d1e0ec', fillOpacity: 1}, tooltip: {text: 'Adjust date granularity'}, reset: {iconPath: 'M75 75L41 41C25.9 25.9 0 36.6 0 57.9L0 168c0 13.3 10.7 24 24 24l110.1 0c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24l0 104c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65 0-94.1c0-13.3-10.7-24-24-24z', tooltipText: 'Reset view', fill:'#888', hoverFill: '#333'}}"
},
{
"name": "configRow",
"description": "configurations for the rows",
"init": "{rowHeight: 25, levelIndentWidth: 15, defaultFill: '#40407d'}"
},
{
"name": "configIncludeRoot",
"description": "boolean to indicate whether the root node should be visible",
"init": "false"
},
{
"name": "configInitialDepth",
"description": "the number of levels deep to start with",
"value": 1
},
{
"name": "configVerticalScrollbar",
"description": "configurations for the vertical scroll bar",
"update": "{enabled: actualHeight>adjustedHeight ,innerPadding: 10, track: {width: 10, height: extent([actualHeight, adjustedHeight])[0], fill: '#F3F3F3'}, handle: {height: max((adjustedHeight/actualHeight)*extent([actualHeight, adjustedHeight])[0], 30), fill: '#ddd', hover: {fill: '#888'}}}"
},
{
"name": "resetLevel",
"description": "the number of levels deep to revert to when collapsing all nodes",
"init": "configInitialDepth",
"on": [
{
"events": "@expand-all-collapse-all-buttons:click, @expand-all-collapse-all-button-labels:click",
"update": "!isValid(datum.datum) ? resetLevel : datum.datum.name === 'expandAll' ? 9999999999999 : configInitialDepth"
}
]
},
{
"name": "expandAllClicked",
"description": "boolean indicating whether expandAll was the last element clicked",
"init": "false",
"on": [
{
"events": "@expand-all-collapse-all-buttons:click, @expand-all-collapse-all-button-labels:click",
"update": "!isValid(datum.datum) ? resetLevel : datum.datum.name === 'expandAll' ? true : false"
},
{
"events": "@gantt-row-clickable-rect:click[!event.ctrlKey]",
"update": "false"
}
]
},
{
"name": "collapseAllClicked",
"init": "false",
"on": [
{
"events": "@expand-all-collapse-all-buttons:click, @expand-all-collapse-all-button-labels:click",
"update": "isValid(datum.datum) && datum.datum.name === 'collapseAll'"
},
{
"events": "@gantt-row-clickable-rect:click[!event.ctrlKey]",
"update": "false"
}
]
},
{
"name": "lastClickedNode",
"description": "node in the hierarchy with children that was last expanded or collapsed",
"init": "null",
"on": [
{
"events": {"type": "click", "markname": "gantt-row-clickable-rect"},
"update": "expandCollapseAnimTSafe < 1 ? clickedNode : (isValid(datum) && datum.hasChildren ? datum.id : clickedNode)"
},
{
"events": {"type": "click", "markname": "column-row-clickable-rect"},
"update": "expandCollapseAnimTSafe < 1 ? clickedNode : (isValid(datum) && datum.hasChildren ? datum.id : clickedNode)"
}
]
},
{
"name": "isInitial",
"description": "boolean indicating if this is considered the initial expand/collapse state",
"init": "true",
"on": [
{
"events": "@expand-all-collapse-all-buttons:click, @expand-all-collapse-all-button-labels:click",
"update": "true"
},
{"events": {"signal": "clickedNode"}, "update": "false"}
]
},
{
"name": "expandCollapseButtonDatum",
"description": "the datum associated with the expand/collapse button that is currently being interacted with",
"value": null,
"on": [
{
"events": "@expand-all-collapse-all-buttons:mouseover, @expand-all-collapse-all-button-labels:mouseover",
"update": "datum.datum"
},
{
"events": "@expand-all-collapse-all-buttons:mouseout, @expand-all-collapse-all-button-labels:mouseout",
"update": "null"
}
]
},
{
"name": "xDomainZoomPercentage",
"value": 0.5,
"on": [
{
"events": {
"markname": "granularity-slider-interactive-rect",
"source": "scope",
"type": "pointermove",
"consume": true,
"throttle": 50,
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "ganttHorizontalScrollMapMouseDown ? xDomainZoomPercentage : clamp(x('granularity-slider-interactive-rect')/configDateStepSlider.track.width,0,1)"
},
{
"events": {"signal": "granularityAnimation"},
"update": "granularityAnimation.active ? zoomStart + (zoomTarget - zoomStart) * granularityAnimation.tEased : xDomainZoomPercentage"
},
{
"events": {"signal": "wheel"},
"update": "ctrlKey ? clamp(xDomainZoomPercentage * pow(wheel, pow(span(xDomain)/span(maxXDomain),0.9)), 0.005, 1) : xDomainZoomPercentage"
}
]
},
{
"name": "xDomainAnchorPercentage",
"description": "Tracks the current horizontal scroll percentage. Updates on: Pointer drag events; Clicking the horizontal scroll map; Mouse wheel scrolling (if Shift is pressed); Arrow key presses (ArrowLeft or ArrowRight); Reset animation triggers.",
"value": 0.5,
"on": [
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "gantt-horizontal-scroll-map-interactive-rect",
"throttle": 50,
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "granularitySliderMouseDown ? xDomainAnchorPercentage : clamp((x(group())/ganttDimensions.width), span(xDomain)/2/span(maxXDomain)/2,1-(span(xDomain)/2/span(maxXDomain)/2))"
},
{
"events": {"signal": "anchorAnimation"},
"update": "anchorAnimation.active ? anchorStart + (anchorTarget - anchorStart) * anchorAnimation.tEased : xDomainAnchorPercentage"
},
{
"events": {"signal": "wheel"},
"update": "shiftKey ? clamp(xDomainAnchorPercentage * pow(wheel, pow(span(xDomain)/span(maxXDomain),0.9)), 0.1, 1) : xDomainAnchorPercentage"
},
{
"events": "window:keydown[event.key === 'ArrowLeft' || event.key === 'ArrowRight']",
"update": "clamp(xDomainAnchorPercentage + 0.005 * (event.key === 'ArrowLeft' ? -1 : 1), span(xDomain)/2/span(maxXDomain)/2,1-(span(xDomain)/2/span(maxXDomain)/2))"
}
]
},
{
"name": "verticalScrollPercentage",
"description": "Tracks the current vertical scroll position as a percentage. Updates on: Mouse wheel events (if panAndZoomMode is off); Arrow key presses (ArrowUp or ArrowDown); Dragging interactions within rect-gantt-background; Clicking the vertical scrollbar; When the scrollbar is disabled, resets to 0.",
"value": 0,
"on": [
{
"events": {
"type": "wheel",
"consume": true,
"force": true,
"source": "view",
"filter": ["!event.ctrlKey", "!event.shiftKey"]
},
"update": "panAndZoomMode ? verticalScrollPercentage : clamp(verticalScrollPercentage - (-event.deltaY * pow(4, event.deltaMode) * 0.002 * adjustedHeight / actualHeight), 0, (actualHeight-adjustedHeight)/actualHeight)"
},
{
"events": "window:keydown[event.key === 'ArrowUp' || event.key === 'ArrowDown']",
"update": "clamp(verticalScrollPercentage + verticalScrollIncrement * (event.key === 'ArrowDown' ? 1 : -1), 0, (actualHeight-adjustedHeight)/actualHeight)"
},
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "rect-gantt-background",
"throttle": 30,
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "panAndZoomMode && isValid(yPanStart) && isValid(y(group())) ? clamp(verticalScrollPercentage + (yPanPrevious + (invert('scaleScrollHandleY', y(group()) + range('scaleScrollHandleY')[0]) - yPanStart) - verticalScrollPercentage) * 0.35, 0, (actualHeight - adjustedHeight) / actualHeight) : verticalScrollPercentage"
},
{
"events": "@group_verticalScrollbar:pointerdown",
"update": "panAndZoomMode ? verticalScrollPercentage : !configVerticalScrollbar.enabled ? 0 : clamp(verticalScrollPercentage - (-event.deltaY * pow(4, event.deltaMode) * 0.001), 0, (actualHeight-adjustedHeight)/actualHeight)"
},
{
"events": {
"type": "pointermove",
"source": "scope",
"markname": "group_verticalScrollbar",
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "panAndZoomMode ? verticalScrollPercentage : !configVerticalScrollbar.enabled ? 0 : invert('scaleScrollHandleY', y(group()))"
},
{
"events": {
"type": "pointermove",
"source": "scope",
"markname": "group_verticalScrollbar",
"between": [{"type": "pointerdown"}, {"type": "mouseout"}]
},
"update": "panAndZoomMode ? verticalScrollPercentage : !configVerticalScrollbar.enabled ? 0 : isValid(group()) ? invert('scaleScrollHandleY', y(group())) : verticalScrollPercentage"
},
{
"events": {"signal": "!configVerticalScrollbar.enabled"},
"update": "0"
}
]
},
{
"name": "verticalScrollIncrement",
"description": "Defines the vertical scroll step size as 1% of the scrollable height. Updates dynamically based on actualHeight and adjustedHeight.",
"update": "0.01 * (actualHeight-adjustedHeight)/actualHeight"
},
{
"name": "yPanStart",
"description": "Stores the initial vertical pan position when clicking the background. Updates when clicking rect-gantt-background, clamping the value within scrollable limits.",
"value": null,
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background",
"throttle": 20
},
"update": "clamp(invert('scaleScrollHandleY', y(group()) + range('scaleScrollHandleY')[0]), 0, (actualHeight-adjustedHeight)/actualHeight)"
}
]
},
{
"name": "yPanPrevious",
"description": "Stores the previous vertical scroll percentage. Updates when clicking rect-gantt-background.",
"value": null,
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background"
},
"update": "verticalScrollPercentage"
}
]
},
{
"name": "verticalScrollVelocity",
"description": "Computes the vertical scroll velocity based on pointer movement between pointerdown and pointerup. Uses an easing formula to maintain smooth scrolling.",
"value": 0,
"on": [
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "rect-gantt-background",
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "(clamp(yPanPrevious + ((invert('scaleScrollHandleY', y(group())) - yPanStart)), 0, (actualHeight-adjustedHeight)/actualHeight) - verticalScrollPercentage) * 0.2 + verticalScrollVelocity * 0.8"
}
]
},
{
"name": "ganttHorizontalScrollMapMouseDown",
"description": "A boolean indicating whether the horizontal scroll map is being clicked. Updates on pointer down and resets on pointer up.",
"value": false,
"on": [
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:pointerdown",
"update": "true"
},
{"events": {"type": "pointerup"}, "update": "false"}
]
},
{
"name": "horizontalScrollMapHeight",
"description": "Defines the height of the horizontal scroll map.",
"value": 10
},
{
"name": "xDomainInitial",
"init": "[data('dataset-extent')[0].minStart, data('dataset-extent')[0].maxEnd]"
},
{
"name": "xDomain",
"update": "[scale('xZoomScale0', xDomainZoomPercentage), scale('xZoomScale1', xDomainZoomPercentage)]"
},
{
"name": "xDomainAnchorInitial",
"init": "xDomainInitial[0]+(span(xDomainInitial)/2)"
},
{
"name": "xDomainAnchor",
"init": "xDomainAnchorInitial",
"on": [
{
"events": {"signal": "xDomainAnchorPercentage"},
"update": "scale('xAnchorScale', xDomainAnchorPercentage)"
}
]
},
{
"update": "[xDomainInitial[0]-span(xDomainInitial)/2, xDomainInitial[1]+span(xDomainInitial)/2]",
"name": "maxXDomain"
},
{
"name": "minXDomain",
"update": "[xDomainAnchor-msPerDay/12, xDomainAnchor+msPerDay/12]"
},
{"name": "smallestAllowableXWidth", "value": 20},
{
"name": "granularitySliderMouseDown",
"value": false,
"on": [
{
"events": "@granularity-slider-interactive-rect:pointerdown",
"update": "true"
},
{
"events": "@granularity-slider-interactive-rect:pointerup",
"update": "false"
}
]
},
{
"name": "showDetailsClick",
"init": "{start: now(), end: now(), t: 1, tEased: 1}",
"on": [
{
"events": [{"signal": "showDetails"}],
"update": "showDetailsClick.t === 1 ? {start: now(), end: now()+ configAnimationDuration.showDetails, t:0, tEased: 0} : showDetailsClick"
},
{
"events": {"signal": "timer"},
"update": "showDetailsClick.t < 1 ? {start: showDetailsClick.start, end: showDetailsClick.end, t:(now()-showDetailsClick.start)/(showDetailsClick.end-showDetailsClick.start), tEased: clamp(showDetailsClick.t < 0.5 ? 4 * pow(showDetailsClick.t, 3) : 1 - pow(-2 * showDetailsClick.t + 2, 3) / 2, 0,1)} : {start: showDetailsClick.start, end: showDetailsClick.end, t: 1, tEased: 1}"
}
]
},
{
"name": "ganttPosition",
"init": "{x: data('columnData')[0].totalWidth, y:0, dy: 19}",
"on": [
{
"events": {"signal": "showDetailsClick.tEased"},
"update": "{x: ganttPosition.x + (data('columnData')[0].totalWidth - ganttPosition.x) * showDetailsClick.tEased, y: ganttPosition.y, dy: ganttPosition.dy}"
}
]
},
{
"name": "ganttDimensions",
"update": "{width: width-ganttPosition.x-2, height: adjustedHeight}",
"on": [
{
"events": {"signal": "resetLevel"},
"update": "{width: width-ganttPosition.x-2, height: adjustedHeight}"
}
]
},
{
"name": "xCurrentTopLevelUnit",
"description": "Determines the time unit displayed",
"update": "data('ganttTimeSeriesConfigurations')[0].tickCount"
},
{
"name": "xCurrentSecondaryUnit",
"description": "Determines the time unit displayed",
"update": "data('ganttTimeSeriesConfigurations')[0].secondaryTickCount"
},
{
"name": "xCurrentSecondaryBand",
"update": "scale('xScaleGanttTimeSeries', xDomain[0]+(msPerDay*365/([1,12,365,8760][indexof(['year', 'month', 'day', 'hour'], xCurrentSecondaryUnit)])))"
},
{
"name": "wheel",
"value": 1,
"on": [
{
"events": "wheel!",
"force": true,
"update": "pow(1.001, -event.deltaY * pow(16, event.deltaMode))"
}
]
},
{
"name": "timer",
"init": "now()",
"on": [{"events": {"type": "timer", "throttle": 20}, "update": "now()"}]
},
{
"name": "zoomStart",
"value": 0.5,
"on": [
{
"events": "@granularity-slider-interactive-rect:click",
"update": "xDomainZoomPercentage"
},
{
"events": "@granularity-reset-interactive-rect:click",
"update": "xDomainZoomPercentage"
}
]
},
{
"name": "zoomTarget",
"value": 0.5,
"on": [
{
"events": "@granularity-slider-interactive-rect:click",
"update": "clamp(x('granularity-slider-interactive-rect')/configDateStepSlider.track.width,0,1)"
},
{"events": "@granularity-reset-interactive-rect:click", "update": "0.5"}
]
},
{
"name": "granularityAnimation",
"init": "{start: now(), end: now(), active: false, t: 1, tEased: 1, markName: null}",
"on": [
{
"events": "@granularity-reset-interactive-rect:click",
"update": "{start: now(), end: now()+configAnimationDuration.granularityReset, active: true, t: 0, tEased: 0, markName: 'granularity-reset-interactive-rect'}"
},
{
"events": "@granularity-slider-interactive-rect:click",
"update": "{start: now(), end: now()+configAnimationDuration.granularitySliderClick, active: true, t: 0, tEased: 0, markName: 'granularity-slider-interactive-rect'}"
},
{
"events": {"signal": "timer"},
"update": "now() > granularityAnimation.end ? {start: granularityAnimation.start, end: granularityAnimation.start, active: false, t: 1, tEased: 1, markName: null} : {start: granularityAnimation.start, end: granularityAnimation.end, active: true, t: (now()-granularityAnimation.start)/(granularityAnimation.end-granularityAnimation.start), tEased: granularityAnimation.t < 0.5 ? 4 * pow(granularityAnimation.t, 3) : 1 - pow(-2 * granularityAnimation.t + 2, 3) / 2, markName: granularityAnimation.markName}"
}
]
},
{
"name": "anchorStart",
"value": 0.5,
"on": [
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:click",
"update": "xDomainAnchorPercentage"
},
{
"events": "@granularity-reset-interactive-rect:click",
"update": "xDomainAnchorPercentage"
}
]
},
{
"name": "anchorTarget",
"value": 0.5,
"on": [
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:click",
"update": "clamp((x(group())/ganttDimensions.width), span(xDomain)/2/span(maxXDomain)/2,1-(span(xDomain)/2/span(maxXDomain)/2))"
},
{"events": "@granularity-reset-interactive-rect:click", "update": "0.5"}
]
},
{
"name": "anchorAnimation",
"init": "{start: now(), end: now(), active: false, t: 1, tEased: 1, markName: null}",
"on": [
{
"events": "@granularity-reset-interactive-rect:click",
"update": "{start: now(), end: now()+configAnimationDuration.anchorReset, active: true, t: 0, tEased: 0, markName: 'granularity-reset-interactive-rect'}"
},
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:click",
"update": "{start: now(), end: now()+configAnimationDuration.anchorMapClick, active: true, t: 0, tEased: 0, markName: 'gantt-horizontal-scroll-map-interactive-rect'}"
},
{
"events": {"signal": "timer"},
"update": "now() > anchorAnimation.end ? {start: anchorAnimation.start, end: anchorAnimation.start, active: false, t: 1, tEased: 1, markName: null} : {start: anchorAnimation.start, end: anchorAnimation.end, active: true, t: (now()-anchorAnimation.start)/(anchorAnimation.end-anchorAnimation.start), tEased: anchorAnimation.t < 0.5 ? 4 * pow(anchorAnimation.t, 3) : 1 - pow(-2 * anchorAnimation.t + 2, 3) / 2, markName: anchorAnimation.markName}"
}
]
},
{
"name": "showDetails",
"description": "the currently toggled value for the 'show details' control",
"init": "showDetailsConfig.initialValue",
"on": [
{
"events": "@show-details-interactive-rect:click{750}",
"update": "!showDetails"
}
]
},
{
"name": "panAndZoomMode",
"description": "A boolean indicating whether the visualization is in pan-and-zoom mode. Initialized from configTogglepanAndZoomMode.initialValue. Updates when the pan-and-zoom-mode-interactive-rect is clicked (toggling its value) or when the space bar is pressed.",
"init": "configTogglepanAndZoomMode.initialValue",
"on": [
{
"events": "@pan-and-zoom-mode-interactive-rect:click",
"update": "!panAndZoomMode"
},
{
"events": "window:keydown[event.keyCode === 32]{0, 100}",
"update": "panAndZoomMode ? false : true"
}
]
},
{
"name": "ctrlKey",
"value": false,
"on": [
{"events": "window:keydown[event.ctrlKey]", "update": "true"},
{"events": "window:keyup", "update": "false"}
]
},
{
"name": "shiftKey",
"value": false,
"on": [
{"events": "window:keydown[event.shiftKey]", "update": "true"},
{"events": "window:keyup", "update": "false"}
]
},
{
"name": "currentMaxIndentWidth",
"description": "the current maximum indent width for all visible nodes",
"update": "isArray(data('render-rows')) && length(data('render-rows'))>0 ? (extent(pluck(data('render-rows'), 'depth'))[1]-1)*configRow.levelIndentWidth : 0"
},
{"name": "msPerDay", "value": 86400000},
{
"name": "clickedIdx",
"update": "indexof(pluck(data('preorder'),'id'), clickedNode)"
},
{
"name": "clickedDepth",
"update": "clickedIdx>=0 && length(data('preorder'))>clickedIdx ? data('preorder')[clickedIdx].depth : -1"
},
{
"name": "baseBreak",
"update": "clickedIdx>=0 && length(data('preorder-for-desc'))>clickedIdx ? data('preorder-for-desc')[clickedIdx].cumeBreak : 0"
},
{
"name": "clickedNode",
"value": null,
"on": [
{
"events": {"type": "click", "markname": "gantt-row-clickable-rect"},
"update": "expandCollapseAnimRunning ? clickedNode : (isValid(datum) && datum.hasChildren ? datum.id : clickedNode)"
},
{
"events": {"type": "click", "markname": "column-row-clickable-rect"},
"update": "expandCollapseAnimRunning ? clickedNode : (isValid(datum) && datum.hasChildren ? datum.id : clickedNode)"
}
]
},
{
"name": "clickedNodeTick",
"value": 0,
"on": [
{
"events": [
{"type": "click", "markname": "gantt-row-clickable-rect"},
{"type": "click", "markname": "column-row-clickable-rect"}
],
"update": "clickedNodeTick + 1"
}
]
},
{
"name": "clickedIndex",
"update": "indexof(rowsBeforeIds, clickedNode) >= 0 ? data('rows-before')[indexof(rowsBeforeIds, clickedNode)].index : (indexof(pluck(data('rows-after'),'id'), clickedNode) >= 0 ? data('rows-after')[indexof(pluck(data('rows-after'),'id'), clickedNode)].index : 1)"
},
{
"name": "expandCollapseClick",
"value": {
"start": 0,
"end": 0,
"t": 1,
"tEased": 1,
"nodeId": null,
"nodeIndex": 1,
"action": null
},
"on": [
{
"events": [{"signal": "clickedNodeTick"}],
"update": "{ 'start': now(), 'end': now() + configAnimationDuration.nodeExpandCollapse, 't': 0, 'tEased': 0, 'nodeId': clickedNode, 'nodeIndex': 1, 'action': ((indexof(pluck(data('state-expanded-prev'),'id'), clickedNode) >= 0) && data('state-expanded-prev')[indexof(pluck(data('state-expanded-prev'),'id'), clickedNode)].expanded) ? 'collapse' : 'expand' }"
},
{
"events": "@expand-all-collapse-all-buttons:click, @expand-all-collapse-all-button-labels:click",
"update": "{'start': now(), 'end': now()+configAnimationDuration.nodeExpandCollapse, 't': 0, 'tEased': 0, 'nodeId': null, 'nodeIndex': 1, 'action': datum.datum.name === 'expandAll' ? 'expand' : 'collapse'}"
},
{
"events": {"signal": "timer"},
"update": "expandCollapseClick.t < 1 ? { 'start': expandCollapseClick.start, 'end': expandCollapseClick.end, 't': (now()-expandCollapseClick.start)/(expandCollapseClick.end-expandCollapseClick.start), 'tEased': clamp(expandCollapseClick.t < 0.5 ? 4*pow(expandCollapseClick.t,3) : 1 - pow(-2*expandCollapseClick.t + 2,3)/2, 0,1), 'nodeId': expandCollapseClick.nodeId, 'nodeIndex': expandCollapseClick.nodeIndex, 'action': expandCollapseClick.action } : expandCollapseClick"
}
]
},
{
"name": "hasVisibleChildrenOfClicked",
"update": "isValid(clickedNode) && indexof(pluck(data('rows-before'), 'parentId'), clickedNode) >= 0"
},
{
"name": "hasRowsBefore",
"update": "isValid(data('rows-before')) && length(data('rows-before'))>0"
},
{
"name": "hasRowsAfter",
"update": "isValid(data('rows-after')) && length(data('rows-after'))>0"
},
{
"name": "rowsAfterIds",
"update": "hasRowsAfter ? pluck(data('rows-after'),'id') : []"
},
{
"name": "hasHeight",
"update": "isValid(data('height')) && length(data('height'))>0"
},
{
"name": "heightNow",
"update": "hasHeight ? data('height')[0]['height'] : 0"
},
{
"name": "expandCollapseAnimTSafe",
"update": "isValid(expandCollapseClick) ? (isValid(expandCollapseClick.t) ? clamp(expandCollapseClick.t, 0, 1) : 1) : 1"
},
{
"name": "expandCollapseAnimTEasedSafe",
"update": "isValid(expandCollapseClick) ? (isValid(expandCollapseClick.tEased) ? clamp(expandCollapseClick.tEased, 0, 1) : 1) : 1"
},
{
"name": "expandCollapseAnimRunning",
"update": "isValid(expandCollapseClick) ? (isValid(expandCollapseClick.t) ? expandCollapseClick.t < 1 : false) : false"
},
{
"name": "snapshotNow",
"value": 0,
"on": [
{
"events": [
{"type":"click","markname":"gantt-row-clickable-rect"},
{"type":"click","markname":"column-row-clickable-rect"},
{"type":"click","markname":"expand-all-collapse-all-button"},
{"type":"click","markname":"expand-all-collapse-all-button-labels"}
],
"update": "now()"
}
]
},
{
"name": "rowsBeforeIds",
"value": [],
"on": [
{
"events": [{"signal":"snapshotNow"}],
"update": "length(data('visible-after'))>0 ? pluck(data('visible-after'),'id') : pluck(data('visible-initial'),'id')"
},
{
"events": [{"signal":"resetLevel"}],
"update": "pluck(data('visible-initial'),'id')"
}
]
},
{
"name": "actualHeight",
"description": "Computes the total height based on the number of all time-series blocks times configRow.rowHeight. Updates dynamically when the dataset changes",
"update": "data('height-animation')[0]['animatedHeight']"
},
{
"name": "adjustedHeight",
"description": "The initial height of the visualization, set to 300. Rows that go beyond this height will require scrolling/panning",
"update": "min((isPowerBIVisual ? (containerSize()[1] || windowSize()[1]) : desiredHeight), actualHeight)",
"on": [
{
"events": {"signal": "resetLevel"},
"update": "min((isPowerBIVisual ? (containerSize()[1] || windowSize()[1]) : desiredHeight), actualHeight)"
}
]
},
{
"name": "safeHeight",
"update": "(isValid(data('height')) && length(data('height'))>0) ? heightNow : 0"
},
{
"name": "height",
"update": "min(data('height-animation')[0].animatedHeight, adjustedHeight)"
},
{
"name": "width",
"update": "isPowerBIVisual ? containerSize()[0] || windowSize()[0] : desiredWidth"
}
],
"marks": [
{
"name": "group-everything",
"type": "group",
"encode": {
"update": {
"width": {"signal": "width"},
"height": {"signal": "height"},
"fill": {"signal": "background"}
}
},
"marks": [
{
"name": "debug-text",
"type": "text",
"encode": {
"update": {
"y": {"value": -100},
"fontSize": {"value": 11},
"fill": {"value": "#888"},
"text": {
"signal": "'DEBUG: clicked:' + (clickedNode||'—') + ' | action:' + (expandCollapseClick.action||'—') + ' | t=' + format(expandCollapseAnimTSafe, '.2f') + ' | before=' + length(isArray(data('visible-before')) ? data('visible-before') : []) + ' | after=' + length(isArray(data('visible-after')) ? data('visible-after') : [])"
}
}
}
},
{
"name": "group-header",
"type": "group",
"encode": {"enter": {"y": {"value": -65}}},
"signals": [
{
"name": "panAndZoomModeClick",
"init": "{start: now(), end: now(), t: 1, tEased: 1}",
"on": [
{
"events": "@pan-and-zoom-mode-interactive-rect:click",
"update": "{start: now(), end: now()+250, t:0, tEased: 0}"
},
{
"events": {"signal": "timer"},
"update": "panAndZoomModeClick.t < 1 ? {start: panAndZoomModeClick.start, end: panAndZoomModeClick.end, t:(now()-panAndZoomModeClick.start)/(panAndZoomModeClick.end-panAndZoomModeClick.start), tEased: clamp(panAndZoomModeClick.t < 0.5 ? 4 * pow(panAndZoomModeClick.t, 3) : 1 - pow(-2 * panAndZoomModeClick.t + 2, 3) / 2, 0,1)} : {start: panAndZoomModeClick.start, end: panAndZoomModeClick.end, t: 1, tEased: 1}"
}
]
},
{
"name": "showDetailsClick",
"init": "{start: now(), end: now(), t: 1, tEased: 1}",
"on": [
{
"events": [{"signal": "showDetails"}],
"update": "{start: now(), end: now()+250, t:0, tEased: 0}"
},
{
"events": {"signal": "timer"},
"update": "showDetailsClick.t < 1 ? {start: showDetailsClick.start, end: showDetailsClick.end, t:(now()-showDetailsClick.start)/(showDetailsClick.end-showDetailsClick.start), tEased: clamp(showDetailsClick.t < 0.5 ? 4 * pow(showDetailsClick.t, 3) : 1 - pow(-2 * showDetailsClick.t + 2, 3) / 2, 0,1)} : {start: showDetailsClick.start, end: showDetailsClick.end, t: 1, tEased: 1}"
}
]
},
{
"name": "showDetailsStart",
"init": "!showDetails ? (showDetailsConfig.track.width - showDetailsConfig.track.height*0.9) : (showDetailsConfig.track.height*0.9)",
"on": [
{
"events": "@show-details-interactive-rect:click",
"update": "!showDetails ? (showDetailsConfig.track.width - showDetailsConfig.track.height*0.9) : (showDetailsConfig.track.height*0.9)"
}
]
},
{
"name": "showDetailsTarget",
"init": "showDetails ? (showDetailsConfig.track.width - showDetailsConfig.track.height*0.9) : (showDetailsConfig.track.height*0.9)",
"on": [
{
"events": "@show-details-interactive-rect:click",
"update": "showDetails ? (showDetailsConfig.track.width - showDetailsConfig.track.height*0.9) : (showDetailsConfig.track.height*0.9)"
}
]
}
],
"marks": [
{
"name": "group-expand-all-collapse-all-buttons",
"description": "the group of marks that make up the expand/collapse buttons",
"type": "group",
"marks": [
{
"name": "label-expand-collapse-text",
"description": "the title for the control",
"type": "text",
"interactive": false,
"encode": {
"enter": {
"x": {
"signal": "data('expandAllCollapseAllConfig')[0]['rowNumber']*(12000*data('expandAllCollapseAllConfig')[0]['size'])"
},
"text": {"signal": "configButtons.label.text || ''"},
"baseline": {"value": "top"},
"font": {"signal": "configButtons.label.font"},
"fontSize": {"signal": "configButtons.label.fontSize"},
"fontStyle": {"signal": "configButtons.label.fontStyle"},
"align": {"value": "left"},
"fill": {"signal": "configButtons.label.fill"}
}
}
},
{
"name": "expand-all-collapse-all-dummy-button-labels",
"description": "icon path marks that will be used by other button marks for reactive geometry",
"type": "symbol",
"from": {"data": "expandAllCollapseAllConfig"},
"interactive": false,
"encode": {
"enter": {
"x": {
"signal": "configButtons.padding+(datum.rowNumber+1)*(12000*datum.size)",
"offset": {
"signal": "data('label-expand-collapse-text')[0].bounds.x2"
}
},
"y": {
"signal": "-1",
"offset": {"signal": "datum.rowNumber === 1 ? 0 : -0.6"}
},
"size": {"field": "size"},
"shape": {"field": "path"},
"fill": {"value": "#999"}
},
"update": {
"fill": {
"signal": "isValid(expandCollapseButtonDatum) && expandCollapseButtonDatum.rowNumber === datum.rowNumber ? 'steelblue' : '#999'"
}
}
}
},
{
"name": "expand-all-collapse-all-button-background",
"description": "the background for the expand/collapse buttons",
"type": "rect",
"from": {
"data": "expand-all-collapse-all-dummy-button-labels"
},
"interactive": true,
"encode": {
"enter": {
"x": {"signal": "datum.bounds.x1-configButtons.padding"},
"x2": {"signal": "datum.bounds.x2+configButtons.padding"},
"y": {"signal": "datum.bounds.y1-configButtons.padding"},
"y2": {"signal": "datum.bounds.y2+configButtons.padding"},
"cornerRadiusTopLeft": {
"signal": "datum.datum.rowNumber == 0 ? 5 : 0"
},
"cornerRadiusBottomLeft": {
"signal": "datum.datum.rowNumber == 0 ? 5 : 0"
},
"cornerRadiusTopRight": {
"signal": "datum.datum.rowNumber == 1 ? 5 : 0"
},
"cornerRadiusBottomRight": {
"signal": "datum.datum.rowNumber == 1 ? 5 : 0"
},
"fillOpacity": {"value": 1},
"stroke": {"signal": "background || '#fff'"},
"strokeWidth": {"value": 1},
"fill": {"signal": "background || '#fff'"},
"opacity": {"value": 1},
"cursor": {"value": "pointer"},
"tooltip": {"signal": "datum.datum.tooltip"}
}
}
},
{
"name": "expand-all-collapse-all-buttons",
"description": "the symbols that appear on the button faces",
"type": "rect",
"from": {
"data": "expand-all-collapse-all-dummy-button-labels"
},
"interactive": true,
"encode": {
"enter": {
"x": {"signal": "datum.bounds.x1-configButtons.padding"},
"x2": {"signal": "datum.bounds.x2+configButtons.padding"},
"y": {"signal": "datum.bounds.y1-configButtons.padding"},
"y2": {"signal": "datum.bounds.y2+configButtons.padding"},
"cornerRadiusTopLeft": {
"signal": "datum.datum.rowNumber == 0 ? 5 : 0"
},
"cornerRadiusBottomLeft": {
"signal": "datum.datum.rowNumber == 0 ? 5 : 0"
},
"cornerRadiusTopRight": {
"signal": "datum.datum.rowNumber == 1 ? 5 : 0"
},
"cornerRadiusBottomRight": {
"signal": "datum.datum.rowNumber == 1 ? 5 : 0"
},
"fillOpacity": {"value": 0.25},
"stroke": {"signal": "configButtons.outerStroke"},
"strokeWidth": {"value": 1},
"cursor": {"value": "pointer"},
"tooltip": {"signal": "datum.datum.tooltip"}
},
"update": {
"fill": {
"signal": "isValid(expandCollapseButtonDatum) && expandCollapseButtonDatum.rowNumber === datum.datum.rowNumber ? configButtons.hoverFill : configButtons.fill"
}
}
}
},
{
"name": "expand-all-collapse-all-button-labels",
"description": "the actual button face symbols",
"type": "symbol",
"from": {
"data": "expand-all-collapse-all-dummy-button-labels"
},
"interactive": true,
"encode": {
"enter": {
"x": {"signal": "datum.x"},
"y": {"signal": "datum.y"},
"size": {"field": "size"},
"shape": {"signal": "datum.datum.path"},
"fill": {"field": "fill"},
"cursor": {"value": "pointer"},
"tooltip": {"signal": "datum.datum.tooltip"}
},
"update": {
"fill": {
"signal": "isValid(expandCollapseButtonDatum) && expandCollapseButtonDatum.rowNumber === datum.datum.rowNumber ? 'steelblue' : '#999'"
}
}
}
}
]
},
{
"name": "group-show-details",
"type": "group",
"from": {"data": "group-expand-all-collapse-all-buttons"},
"interactive": false,
"clip": false,
"encode": {
"enter": {
"x": {"signal": "datum.bounds.x2+showDetailsConfig.xOffset"},
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
}
}
},
"marks": [
{
"name": "show-details-label",
"description": "the title for the toggle control",
"type": "text",
"interactive": false,
"encode": {
"update": {
"x": {"signal": "0"},
"text": {"signal": "showDetailsConfig.label.text"},
"baseline": {"value": "middle"},
"font": {"signal": "showDetailsConfig.label.font"},
"fontSize": {
"signal": "showDetailsConfig.label.fontSize"
},
"fontStyle": {
"signal": "showDetailsConfig.label.fontStyle"
},
"align": {"value": "left"},
"fill": {"signal": "showDetailsConfig.label.fill"}
}
}
},
{
"name": "show-details-track-rect",
"description": "the track for the toggle control",
"type": "rect",
"from": {"data": "show-details-label"},
"interactive": false,
"encode": {
"enter": {
"x": {
"signal": "datum.bounds.x1 + datum.bounds.x2",
"offset": {"signal": "showDetailsConfig.label.dx"}
},
"y": {"signal": "-(datum.bounds.y2-datum.bounds.y1)/3"}
},
"update": {
"height": {"signal": "showDetailsConfig.track.height"},
"width": {"signal": "showDetailsConfig.track.width"},
"cornerRadius": {
"signal": "showDetailsConfig.track.cornerRadius"
},
"fill": {"signal": "showDetailsConfig.track.fill"},
"stroke": {"signal": "showDetailsConfig.track.stroke"},
"strokeWidth": {
"signal": "showDetailsConfig.track.strokeWidth"
}
}
}
},
{
"name": "show-details-track-rect-on",
"description": "the track for the toggle control",
"type": "rect",
"from": {"data": "show-details-track-rect"},
"interactive": false,
"encode": {
"enter": {"x": {"field": "x"}, "y": {"field": "y"}},
"update": {
"height": {"field": "height"},
"width": {"field": "width"},
"cornerRadius": {"field": "cornerRadius"},
"fill": {"signal": "showDetailsConfig.on.fill"},
"stroke": {"field": "stroke"},
"strokeWidth": {"field": "strokeWidth"},
"fillOpacity": {
"signal": "showDetails ? showDetailsClick.tEased : 1-showDetailsClick.tEased"
},
"strokeOpacity": {
"signal": "showDetails ? showDetailsClick.tEased : 1-showDetailsClick.tEased"
}
}
}
},
{
"name": "group-show-details-toggle-outer-arc",
"type": "group",
"from": {"data": "show-details-track-rect"},
"transform": [
{
"type": "formula",
"expr": "showDetailsClick.tEased",
"as": "tEased"
},
{
"type": "formula",
"expr": "showDetailsStart + (showDetailsTarget - showDetailsStart) * showDetailsClick.tEased",
"as": "x"
}
],
"encode": {
"enter": {
"x": {"signal": "datum.bounds.x1"},
"y": {
"signal": "datum.bounds.y1+showDetailsConfig.track.height/2"
}
}
},
"marks": [
{
"name": "show-details-toggle-outer-arc",
"description": "the circle mark that serves as the the 'toggle handle'",
"from": {"data": "show-details-track-rect"},
"type": "arc",
"interactive": false,
"encode": {
"enter": {
"innerRadius": {"value": 0},
"outerRadius": {
"signal": "showDetailsConfig.track.height*0.9"
},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "showDetailsConfig.handle.stroke || '#BBB'"
},
"strokeWidth": {
"signal": "showDetailsConfig.handle.strokeWidth"
},
"fill": {
"signal": "showDetailsConfig.handle.fill || '#fff'"
}
},
"update": {"x": {"signal": "datum.x"}}
}
},
{
"name": "show-details-interactive-rect",
"type": "rect",
"from": {"data": "show-details-track-rect"},
"interactive": true,
"encode": {
"update": {
"y": {
"signal": "datum.bounds.y1-(showDetailsConfig.track.height*0.5)"
},
"y2": {
"signal": "datum.bounds.y2+showDetailsConfig.track.height*0.5"
},
"x": {"signal": "-(datum.bounds.x2-datum.bounds.x1)"},
"x2": {"signal": "datum.bounds.x2"},
"fill": {"value": "transparent"},
"tooltip": {
"signal": "showDetailsConfig.tooltip.text"
},
"cursor": {"value": "pointer"}
}
}
}
]
}
]
},
{
"name": "group-pan-and-zoom-mode",
"description": "group for the marks that make up the toggle control to hide/show details",
"type": "group",
"interactive": false,
"clip": false,
"encode": {
"enter": {
"x": {"signal": "ganttPosition.x+ganttDimensions.width"}
}
},
"marks": [
{
"name": "pan-and-zoom-mode-text-zoom",
"description": "the title for the toggle control",
"type": "text",
"interactive": false,
"encode": {
"update": {
"x": {"signal": "0"},
"y": {
"signal": "configTogglepanAndZoomMode.track.height/2"
},
"text": {"signal": "'Pan & Zoom Mode'"},
"fontWeight": {
"signal": "panAndZoomMode ? 'bold' : 'regular'"
},
"baseline": {"value": "top"},
"font": {
"signal": "configTogglepanAndZoomMode.label.font"
},
"fontSize": {
"signal": "configTogglepanAndZoomMode.label.fontSize"
},
"fontStyle": {
"signal": "configTogglepanAndZoomMode.label.fontStyle"
},
"align": {"value": "right"},
"fill": {
"signal": "configTogglepanAndZoomMode.label.fill"
}
}
}
},
{
"name": "pan-and-zoom-mode-track-rect",
"description": "the track for the toggle control",
"type": "rect",
"from": {"data": "pan-and-zoom-mode-text-zoom"},
"interactive": false,
"encode": {
"enter": {
"x": {
"signal": "datum.bounds.x1-configTogglepanAndZoomMode.track.width*2",
"offset": {
"signal": "-configTogglepanAndZoomMode.label.dx"
}
}
},
"update": {
"y": {"signal": "(datum.bounds.y2-datum.bounds.y1)/2"},
"height": {
"signal": "configTogglepanAndZoomMode.track.height"
},
"width": {
"signal": "configTogglepanAndZoomMode.track.width"
},
"cornerRadius": {
"signal": "configTogglepanAndZoomMode.track.cornerRadius"
},
"fill": {
"signal": "panAndZoomMode ? configTogglepanAndZoomMode.on.fill : configTogglepanAndZoomMode.track.fill"
},
"stroke": {
"signal": "configTogglepanAndZoomMode.track.stroke"
},
"strokeWidth": {
"signal": "configTogglepanAndZoomMode.track.strokeWidth"
}
}
}
},
{
"name": "group-pan-and-zoom-mode-toggle-outer-arc",
"type": "group",
"from": {"data": "pan-and-zoom-mode-track-rect"},
"transform": [
{
"type": "formula",
"expr": "panAndZoomModeClick.tEased",
"as": "tEased"
},
{
"type": "formula",
"expr": "configTogglepanAndZoomMode.track.height*0.9",
"as": "scrollX"
},
{
"type": "formula",
"expr": "configTogglepanAndZoomMode.track.width - configTogglepanAndZoomMode.track.height*0.9",
"as": "panX"
},
{
"type": "formula",
"expr": "panAndZoomMode ? ((datum.panX-datum.scrollX)*datum.tEased+datum.scrollX) : (-(datum.panX-datum.scrollX)*datum.tEased+datum.panX)",
"as": "x"
}
],
"encode": {
"enter": {
"x": {"signal": "datum.bounds.x1"},
"y": {
"signal": "datum.bounds.y1+configTogglepanAndZoomMode.track.height/2"
}
}
},
"marks": [
{
"name": "pan-and-zoom-mode-toggle-outer-arc",
"description": "the circle mark that serves as the the 'toggle handle'",
"from": {"data": "pan-and-zoom-mode-track-rect"},
"type": "arc",
"interactive": false,
"encode": {
"enter": {
"innerRadius": {"value": 0},
"outerRadius": {
"signal": "configTogglepanAndZoomMode.track.height*0.9"
},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "configTogglepanAndZoomMode.handle.stroke || '#BBB'"
},
"strokeWidth": {
"signal": "configTogglepanAndZoomMode.handle.strokeWidth"
},
"fill": {
"signal": "configTogglepanAndZoomMode.handle.fill || '#fff'"
}
},
"update": {"x": {"signal": "datum.x"}}
}
}
]
},
{
"name": "pan-and-zoom-mode-toggle-text-scroll",
"description": "the title for the toggle control",
"type": "text",
"from": {"data": "pan-and-zoom-mode-track-rect"},
"interactive": false,
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1"},
"dx": {"signal": "-configTogglepanAndZoomMode.label.dx"},
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {"signal": "'Scroll Mode'"},
"fontWeight": {
"signal": "panAndZoomMode ? 'regular' : 'bold'"
},
"baseline": {"value": "middle"},
"font": {
"signal": "configTogglepanAndZoomMode.label.font"
},
"fontSize": {
"signal": "configTogglepanAndZoomMode.label.fontSize"
},
"fontStyle": {
"signal": "configTogglepanAndZoomMode.label.fontStyle"
},
"align": {"value": "right"},
"fill": {
"signal": "configTogglepanAndZoomMode.label.fill"
}
}
}
}
]
},
{
"name": "pan-and-zoom-mode-interactive-rect",
"type": "rect",
"from": {"data": "group-pan-and-zoom-mode"},
"interactive": true,
"encode": {
"update": {
"y": {"signal": "datum.bounds.y1"},
"y2": {"signal": "datum.bounds.y2"},
"x": {"signal": "datum.bounds.x1-5"},
"x2": {"signal": "datum.bounds.x2"},
"fill": {"value": "#fff"},
"fillOpacity": {"value": 0.15},
"tooltip": {
"signal": "configTogglepanAndZoomMode.tooltip.object"
},
"cursor": {"value": "pointer"}
},
"hover": {"fill": {"value": "transparent"}}
}
}
]
},
{
"name": "column-group",
"type": "group",
"encode": {
"update": {
"x": {"signal": "0"},
"y": {"signal": "0"},
"x2": {"signal": "ganttPosition.x"},
"height": {"signal": "ganttDimensions.height"}
}
},
"marks": [
{
"name": "column-header-group",
"type": "group",
"encode": {
"update": {
"y": {"signal": "-ganttPosition.dy"},
"height": {"signal": "ganttPosition.dy"},
"width": {"signal": "ganttPosition.x"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "column-header-label-group",
"type": "group",
"encode": {
"update": {
"x": {"signal": "configRow.levelIndentWidth"},
"y": {"signal": "1.5"},
"height": {"signal": "ganttPosition.dy"},
"width": {
"signal": "ganttPosition.x-configRow.levelIndentWidth"
},
"clip": {"value": true}
}
},
"marks": [
{
"name": "column-labels",
"description": "column header text labels",
"type": "text",
"from": {"data": "columnData"},
"interactive": false,
"encode": {
"update": {
"text": {"field": "label"},
"x": {"signal": "datum.x"},
"y": {"signal": "ganttPosition.dy/2"},
"baseline": {"value": "middle"},
"align": {"field": "align"},
"fontSize": {"value": 14},
"font": {"value": "Segoe UI"},
"fontWeight": {"value": "600"}
}
}
}
]
}
]
},
{
"name": "column-row-group",
"type": "group",
"encode": {
"update": {
"x": {"signal": "0"},
"y": {"signal": "0"},
"x2": {"signal": "ganttPosition.x"},
"height": {"signal": "ganttDimensions.height"}
}
}
}
]
},
{
"name": "gantt-group",
"type": "group",
"encode": {
"update": {
"x": {"signal": "ganttPosition.x"},
"y": {"signal": "-ganttPosition.dy"},
"width": {"signal": "ganttDimensions.width"},
"height": {"signal": "ganttDimensions.height+ganttPosition.dy"},
"fill": {"signal": "background"}
}
},
"signals": [
{
"name": "ganttHorizontalScrollMapMouseOver",
"description": "A boolean indicating whether the horizontal scroll map is being hovered over. Updates when hovered and resets when the cursor leaves.",
"value": false,
"on": [
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:mouseover",
"update": "true"
},
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:mouseout",
"update": "false"
}
]
}
],
"marks": [
{
"name": "axes-group",
"type": "group",
"data": [
{
"name": "weekendDates",
"values": [{}],
"transform": [
{
"type": "formula",
"expr": "(scale('xScaleGanttTimeSeries', xDomain[0] + msPerDay) - scale('xScaleGanttTimeSeries', xDomain[0]) >= 5) ? timeSequence('day', xDomain[0], xDomain[1]) : []",
"as": "date"
},
{"type": "flatten", "fields": ["date"]},
{
"type": "filter",
"expr": "indexof([0,6], day(datum.date)) >= 0"
}
]
}
],
"marks": [
{
"name": "weekendRects",
"type": "rect",
"from": {"data": "weekendDates"},
"encode": {
"update": {
"x": {"scale": "xScaleGanttTimeSeries", "field": "date"},
"x2": {
"scale": "xScaleGanttTimeSeries",
"signal": "+datum.date+msPerDay-1"
},
"y": {"signal": "ganttPosition.dy"},
"height": {"signal": "ganttDimensions.height"},
"fill": {"value": "#708090"},
"fillOpacity": {"value": 0.1}
}
}
},
{
"name": "secondary-axis-group",
"type": "group",
"encode": {
"update": {
"y": {"signal": "0"},
"width": {"signal": "ganttDimensions.width"},
"height": {"signal": "adjustedHeight + ganttPosition.dy"}
}
},
"data": [
{
"name": "ganttAxisSecondaryLevelLabels",
"values": [{}],
"transform": [
{
"type": "formula",
"expr": "xCurrentSecondaryUnit === 'hour' ? 'hours' : xCurrentSecondaryUnit",
"as": "secondaryUnit"
},
{
"type": "formula",
"expr": "['year', 'month', 'day', 'hours'][indexof(['year', 'month', 'day', 'hours'], datum.secondaryUnit)]",
"as": "intervalUnit"
},
{
"type": "formula",
"expr": "timeSequence(datum.intervalUnit, xDomain[0]-(msPerDay*365/([1,12,365,8760][indexof(['year', 'month', 'day', 'hour'], xCurrentSecondaryUnit)])), xDomain[1]+(msPerDay*365/([1,12,365,8760][indexof(['year', 'month', 'day', 'hour'], xCurrentSecondaryUnit)])))",
"as": "startDate"
},
{"type": "flatten", "fields": ["startDate"]},
{
"type": "formula",
"expr": "datum.startDate",
"as": "date"
},
{
"type": "formula",
"expr": "+datum.startDate",
"as": "startDate"
},
{
"type": "formula",
"expr": "timeFormat(datum.startDate, datum.secondaryUnit === 'year' ? '%Y' : datum.secondaryUnit === 'month' ? '%b-%Y' : datum.secondaryUnit === 'day' ? '%d-%b-%Y' : '%d-%b-%Y %H')",
"as": "groupBy"
},
{
"type": "aggregate",
"ops": ["min", "max"],
"fields": ["startDate", "startDate"],
"groupby": ["secondaryUnit", "groupBy"],
"as": ["startDate", "endDate"]
},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "startDate", "order": "ascending"},
"as": ["index"]
},
{
"type": "window",
"ops": ["lead"],
"fields": ["startDate"],
"sort": {"field": "startDate", "order": "ascending"},
"as": ["leadStartDate"]
},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "startDate", "order": "descending"},
"as": ["descendingIndex"]
},
{
"type": "formula",
"expr": "(datum.startDate === datum.endDate && isValid(datum.leadStartDate) ? datum.leadStartDate : datum.descendingIndex === 1 ? xDomain[1] : datum.endDate)-1",
"as": "endDate"
},
{
"type": "formula",
"expr": "(datum.startDate + (datum.endDate-datum.startDate)/2)",
"as": "axisDate"
},
{
"type": "formula",
"expr": "(scale('xScaleGanttTimeSeries', datum.startDate) + scale('xScaleGanttTimeSeries', datum.endDate)) / 2",
"as": "axisX"
}
]
},
{
"name": "ganttAxisSecondaryGridlines",
"values": [{}],
"transform": [
{
"type": "formula",
"expr": "xCurrentSecondaryUnit === 'hour' ? 'hours' : xCurrentSecondaryUnit",
"as": "secondaryUnit"
},
{
"type": "formula",
"expr": "['year', 'month', 'day', 'hours'][indexof(['year', 'month', 'day', 'hours'], datum.secondaryUnit)]",
"as": "intervalUnit"
},
{
"type": "formula",
"expr": "timeSequence(datum.intervalUnit, xDomain[0]-(msPerDay*365/([1,12,365,8760][indexof(['year', 'month', 'day', 'hour'], xCurrentSecondaryUnit)])), xDomain[1]+(msPerDay*365/([1,12,365,8760][indexof(['year', 'month', 'day', 'hour'], xCurrentSecondaryUnit)])))",
"as": "startDate"
},
{"type": "flatten", "fields": ["startDate"]},
{
"type": "formula",
"expr": "datum.startDate",
"as": "axisDate"
},
{
"type": "formula",
"expr": "scale('xScaleGanttTimeSeries', datum.axisDate)",
"as": "axisX"
}
]
}
],
"marks": [
{
"name": "secondary-axis-level-dummy-axis-labels",
"type": "text",
"from": {"data": "ganttAxisSecondaryLevelLabels"},
"interactive": false,
"encode": {
"update": {
"x": {"field": "axisX"},
"y": {"signal": "ganttPosition.dy*0.535"},
"text": {
"signal": "timeFormat(datum.startDate, (xCurrentSecondaryUnit === 'month' && xCurrentSecondaryBand > 65 ? '%B' : data('ganttTimeSeriesConfigurations')[0].secondaryFormat))"
},
"fontWeight": {"value": "500"},
"align": {"value": "center"},
"baseline": {"value": "middle"},
"fontSize": {"value": 12},
"fill": {"value": "#444"},
"opacity": {"value": 0}
}
}
},
{
"name": "secondary-level-axis-labels",
"type": "text",
"from": {
"data": "secondary-axis-level-dummy-axis-labels"
},
"interactive": true,
"encode": {
"update": {
"x": {"field": "x"},
"y": {"field": "y"},
"text": {
"signal": "(datum.bounds.x1 > 2.5 && datum.bounds.x2 < ganttDimensions.width-2.5) ? datum.text : null"
},
"fontWeight": {"field": "fontWeight"},
"align": {"field": "align"},
"baseline": {"field": "baseline"},
"fontSize": {"field": "fontSize"},
"fill": {"value": "#444"}
}
}
},
{
"name": "secondary-grid-lines",
"type": "rect",
"from": {"data": "ganttAxisSecondaryGridlines"},
"encode": {
"update": {
"x": {"signal": "datum.axisX-0.5"},
"width": {
"signal": "configGanttGridlines.secondaryAxis.strokeWidth"
},
"height": {
"signal": "ganttDimensions.height+ganttPosition.dy"
},
"fill": {
"signal": "configGanttGridlines.secondaryAxis.stroke"
}
}
}
}
]
},
{
"name": "top-level-axis-group",
"type": "group",
"encode": {
"update": {
"x": {"signal": "0"},
"y": {"signal": "-ganttPosition.dy"},
"width": {
"signal": "!isValid(xCurrentTopLevelUnit) ? 0 : ganttDimensions.width"
},
"height": {
"signal": "ganttPosition.dy*2+ganttDimensions.height"
},
"strokeWidth": {"value": 1}
}
},
"data": [
{
"name": "ganttAxisTopLevelLabels",
"values": [{}],
"transform": [
{
"type": "formula",
"expr": "xCurrentTopLevelUnit === 'hour' ? 'hours' : xCurrentTopLevelUnit",
"as": "topLevelUnit"
},
{
"type": "formula",
"expr": "xCurrentSecondaryUnit === 'hour' ? 'hours' : xCurrentSecondaryUnit",
"as": "secondaryUnit"
},
{
"type": "formula",
"expr": "['day', 'hours', 'minutes', 'seconds'][indexof(['year', 'month', 'day', 'hours'], datum.secondaryUnit)]",
"as": "intervalUnit"
},
{
"type": "formula",
"expr": "timeSequence(datum.intervalUnit, xDomain[0], xDomain[1])",
"as": "startDate"
},
{"type": "flatten", "fields": ["startDate"]},
{
"type": "formula",
"expr": "datum.startDate",
"as": "date"
},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "startDate", "order": "ascending"},
"as": ["index"]
},
{
"type": "formula",
"expr": "+datum.startDate",
"as": "startDate"
},
{
"type": "formula",
"expr": "timeFormat(datum.startDate, datum.topLevelUnit === 'year' ? '%Y' : datum.topLevelUnit === 'month' ? '%b-%Y' : datum.topLevelUnit === 'day' ? '%d-%b-%Y' : '%d-%b-%Y %H')",
"as": "groupBy"
},
{
"type": "aggregate",
"ops": ["min", "max"],
"fields": ["startDate", "startDate"],
"groupby": ["topLevelUnit", "groupBy"],
"as": ["startDate", "endDate"]
},
{
"type": "window",
"ops": ["rank"],
"sort": {"field": "startDate", "order": "ascending"},
"groupby": ["groupBy"],
"as": ["rankWithinGroup"]
},
{
"type": "filter",
"expr": "datum.rankWithinGroup === 1"
},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "startDate", "order": "ascending"},
"as": ["index"]
},
{
"type": "window",
"ops": ["lead"],
"fields": ["startDate"],
"sort": {"field": "startDate", "order": "ascending"},
"as": ["leadStartDate"]
},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "startDate", "order": "descending"},
"as": ["descendingIndex"]
},
{
"type": "formula",
"expr": "(datum.startDate === datum.endDate && isValid(datum.leadStartDate) ? datum.leadStartDate : datum.descendingIndex === 1 ? xDomain[1] : datum.endDate)-1",
"as": "endDate"
},
{
"type": "formula",
"expr": "(datum.startDate + (datum.endDate-datum.startDate)/2)",
"as": "axisDate"
},
{
"type": "formula",
"expr": "(scale('xScaleGanttTimeSeries', datum.startDate) + scale('xScaleGanttTimeSeries', datum.endDate)) / 2",
"as": "axisX"
},
{
"type": "filter",
"expr": "isValid(datum.topLevelUnit)"
}
]
},
{
"name": "ganttAxisTopGridlines",
"values": [{}],
"transform": [
{
"type": "filter",
"expr": "isValid(xCurrentTopLevelUnit)"
},
{
"type": "formula",
"expr": "xCurrentTopLevelUnit === 'hour' ? 'hours' : xCurrentTopLevelUnit",
"as": "topLevelUnit"
},
{
"type": "formula",
"expr": "['year', 'month', 'day', 'hours'][indexof(['year', 'month', 'day', 'hours'], datum.topLevelUnit)]",
"as": "intervalUnit"
},
{
"type": "formula",
"expr": "timeSequence(datum.intervalUnit, xDomain[0]-(msPerDay*365/([1,12,365,8760][indexof(['year', 'month', 'day', 'hour'], xCurrentTopLevelUnit)])), xDomain[1]+(msPerDay*365/([1,12,365,8760][indexof(['year', 'month', 'day', 'hour'], xCurrentTopLevelUnit)])))",
"as": "startDate"
},
{"type": "flatten", "fields": ["startDate"]},
{
"type": "formula",
"expr": "datum.startDate",
"as": "axisDate"
},
{
"type": "formula",
"expr": "scale('xScaleGanttTimeSeries', datum.axisDate)",
"as": "axisX"
}
]
}
],
"marks": [
{
"name": "top-level-dummy-axis-labels",
"type": "text",
"from": {"data": "ganttAxisTopLevelLabels"},
"interactive": false,
"encode": {
"update": {
"x": {"field": "axisX"},
"y": {"signal": "ganttPosition.dy/2"},
"dy": {"signal": "1.5"},
"text": {
"signal": "timeFormat(datum.startDate, data('ganttTimeSeriesConfigurations')[0].format)"
},
"fontWeight": {"value": "600"},
"align": {"value": "center"},
"baseline": {"value": "middle"},
"fontSize": {"value": 13},
"fill": {"value": "#666"},
"opacity": {"value": 0}
}
}
},
{
"name": "top-level-axis-labels",
"type": "text",
"from": {"data": "top-level-dummy-axis-labels"},
"interactive": true,
"encode": {
"update": {
"x": {"field": "x"},
"y": {"field": "y"},
"dy": {"field": "dy"},
"text": {
"signal": "(datum.bounds.x1 > 2.5 && datum.bounds.x2 < ganttDimensions.width-2.5) ? datum.text : null"
},
"fontWeight": {"field": "fontWeight"},
"align": {"field": "align"},
"baseline": {"field": "baseline"},
"fill": {"field": "fill"},
"fontSize": {"field": "fontSize"}
}
}
},
{
"name": "top-grid-lines",
"type": "rect",
"from": {"data": "ganttAxisTopGridlines"},
"encode": {
"update": {
"x": {"signal": "datum.axisX-0.5"},
"width": {
"signal": "inrange(datum.axisX, [-1.5,1.5]) || inrange(datum.axisX, [ganttDimensions.width-1.5, ganttDimensions.width+1.5]) ? 0 : configGanttGridlines.topAxis.strokeWidth"
},
"height": {
"signal": "ganttDimensions.height+ganttPosition.dy*2"
},
"fill": {
"signal": "configGanttGridlines.topAxis.stroke"
}
}
}
}
]
}
]
},
{
"name": "gantt-canvas-static-group",
"type": "group",
"encode": {
"update": {
"y": {"signal": "ganttPosition.dy"},
"width": {"signal": "ganttDimensions.width"},
"height": {"signal": "ganttDimensions.height"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "gantt-canvas-group",
"type": "group",
"encode": {
"update": {
"y": {
"signal": "actualHeight > adjustedHeight ? clamp(-verticalScrollPercentage*actualHeight,-(actualHeight-adjustedHeight), 2*ganttPosition.dy) : 0"
},
"width": {"signal": "ganttDimensions.width"},
"height": {"signal": "actualHeight"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "gantt-row-clickable-rect",
"description": "the invisible rect used for interactions for each row that spans across the visual horizontally",
"type": "rect",
"from": {"data": "render-rows"},
"interactive": true,
"encode": {
"update": {
"tooltip": {"signal": "datum.name"},
"x": {"field": "x"},
"x2": {"field": "x2"},
"y": {"field": "yWithPadding"},
"y2": {"field": "y2WithPadding"},
"cursor": {
"signal": "datum.hasChildren ? 'pointer' : 'default'"
},
"fill": {"field": "color"}
}
}
}
]
}
]
},
{
"name": "group-gantt-horizontal-scroll-map",
"type": "group",
"encode": {
"update": {
"x": {"signal": "0"},
"y": {
"signal": "ganttPosition.y+ganttPosition.dy+ganttDimensions.height+7.5"
}
}
},
"marks": [
{
"name": "track",
"type": "rect",
"encode": {
"update": {
"width": {"signal": "ganttDimensions.width"},
"y": {"signal": "horizontalScrollMapHeight/2"},
"height": {"value": 0.3},
"fill": {"value": "#999"}
}
}
},
{
"name": "rect-domain-max-brush",
"type": "rect",
"encode": {
"update": {
"x": {
"signal": "scale('xScaleGanttTimeSeriesMax', xDomain[0])",
"offset": {
"signal": "(ganttHorizontalScrollMapMouseDown ? 0 : 0) -0.5"
}
},
"x2": {
"signal": "scale('xScaleGanttTimeSeriesMax', xDomain[1])",
"offset": {
"signal": "(ganttHorizontalScrollMapMouseDown ? 0 : 0) + 0.5"
}
},
"height": {"signal": "horizontalScrollMapHeight"},
"fill": {
"signal": "ganttHorizontalScrollMapMouseOver ? '#f3f7fa' : '#fff'"
}
}
}
},
{
"name": "time-series-rect",
"type": "rect",
"encode": {
"update": {
"x": {
"signal": "scale('xScaleGanttTimeSeriesMax', xDomainInitial[0])"
},
"x2": {
"signal": "scale('xScaleGanttTimeSeriesMax', xDomainInitial[1])"
},
"y": {"signal": "horizontalScrollMapHeight/2"},
"strokeWidth": {"signal": "horizontalScrollMapHeight/4"},
"stroke": {"value": "#CCC"},
"fill": {"value": "transparent"},
"cursor": {
"signal": "(ganttHorizontalScrollMapMouseOver || ganttHorizontalScrollMapMouseDown) && scale('xScaleGanttTimeSeries', xDomain[0]) !== range('xScaleGanttTimeSeries')[0] ? 'pointer' : 'default'"
}
}
}
},
{
"name": "rect-domain-min-tick-tick",
"from": {"data": "rect-domain-max-brush"},
"type": "text",
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1-2"},
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {"signal": "'|'"},
"baseline": {"value": "middle"},
"fill": {"signal": "'#999'"},
"fontWeight": {"value": "600"},
"fontSize": {"value": 12}
}
}
},
{
"name": "rect-domain-max-tick",
"from": {"data": "rect-domain-max-brush"},
"type": "text",
"encode": {
"update": {
"x": {"signal": "datum.bounds.x2-1"},
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {"signal": "'|'"},
"baseline": {"value": "middle"},
"fill": {"signal": "'#999'"},
"fontWeight": {"value": "600"},
"fontSize": {"value": 12}
}
}
}
]
},
{
"name": "group-date-granularity",
"description": "the group of marks that makes up the slider control",
"type": "group",
"interactive": false,
"encode": {
"update": {
"x": {"signal": "ganttDimensions.width"},
"y": {
"signal": "height+configDateStepSlider.track.height*2+30"
},
"height": {"signal": "configDateStepSlider.track.height*2"}
}
},
"signals": [
{
"name": "granularityResetMouseover",
"description": "A boolean indicating whether the reset granularity button is being hovered over.",
"value": false,
"on": [
{
"events": "@granularity-reset-interactive-rect:mouseover",
"update": "true"
},
{
"events": "@granularity-reset-interactive-rect:mouseout",
"update": "false"
}
]
}
],
"marks": [
{
"name": "group-granularity-reset",
"type": "group",
"marks": [
{
"name": "text-date-granularity-reset-icon",
"type": "symbol",
"encode": {
"update": {
"x": {"signal": "-15"},
"y": {"signal": "0"},
"shape": {
"value": "'M75 75L41 41C25.9 25.9 0 36.6 0 57.9L0 168c0 13.3 10.7 24 24 24l110.1 0c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24l0 104c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65 0-94.1c0-13.3-10.7-24-24-24z'"
},
"size": {"signal": "0.0025"},
"fill": {
"signal": "granularityResetMouseover ? '#333' : '#888'"
}
}
}
},
{
"name": "text-date-granularity-reset-label",
"type": "text",
"from": {"data": "text-date-granularity-reset-icon"},
"encode": {
"enter": {
"x": {
"signal": "datum.bounds.x1-configDateStepSlider.innerPadding/2"
}
},
"update": {
"y": {
"signal": "(datum.bounds.y2-datum.bounds.y1)/2+1"
},
"align": {"value": "right"},
"baseline": {"value": "middle"},
"text": {"signal": "'Reset'"},
"fontSize": {
"signal": "configDateStepSlider.label.fontSize"
},
"fill": {
"signal": "granularityResetMouseover ? '#222' : '#666'"
}
}
}
}
]
},
{
"name": "group-date-granularity-slider",
"type": "group",
"marks": [
{
"name": "text-date-granularity-track-rect",
"from": {"data": "group-granularity-reset"},
"description": "the track for the slider control",
"type": "rect",
"interactive": false,
"encode": {
"enter": {
"x": {
"signal": "datum.bounds.x1-configDateStepSlider.innerPadding-configDateStepSlider.track.width"
}
},
"update": {
"y": {
"signal": "(datum.bounds.y2-datum.bounds.y1)/2-configDateStepSlider.track.height/4"
},
"height": {
"signal": "configDateStepSlider.track.height"
},
"width": {
"signal": "configDateStepSlider.track.width"
},
"cornerRadius": {
"signal": "configDateStepSlider.track.cornerRadius"
},
"fill": {"signal": "configDateStepSlider.track.fill"},
"stroke": {
"signal": "configDateStepSlider.track.stroke"
},
"strokeWidth": {
"signal": "configDateStepSlider.track.strokeWidth"
}
}
}
},
{
"name": "slider-percentage-rect",
"description": "the rect that indicates the slider percentage",
"type": "rect",
"from": {"data": "text-date-granularity-track-rect"},
"interactive": false,
"encode": {
"update": {
"height": {
"signal": "configDateStepSlider.track.height"
},
"x": {"signal": "datum.bounds.x1"},
"y": {"signal": "datum.bounds.y1+1"},
"width": {
"signal": "xDomainZoomPercentage*configDateStepSlider.track.width"
},
"cornerRadius": {
"signal": "configDateStepSlider.track.cornerRadius"
},
"fill": {
"signal": "configDateStepSlider.progress.fill"
},
"fillOpacity": {
"signal": "configDateStepSlider.progress.fillOpacity"
},
"stroke": {
"signal": "configDateStepSlider.track.stroke"
},
"strokeWidth": {
"signal": "configDateStepSlider.track.strokeWidth"
}
}
}
},
{
"name": "handles-outer-arc",
"description": "the outer circle mark that serves as the the 'slider handle'",
"from": {"data": "slider-percentage-rect"},
"type": "arc",
"interactive": false,
"encode": {
"enter": {
"y": {
"signal": "datum.bounds.y1+(configDateStepSlider.track.height)/2"
},
"innerRadius": {"value": 0},
"outerRadius": {
"signal": "configDateStepSlider.track.height*0.9"
},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "configDateStepSlider.handle.outerStroke || '#BBB'"
},
"strokeWidth": {
"signal": "configDateStepSlider.handle.outerStrokeWidth"
},
"fill": {
"signal": "configDateStepSlider.handle.outerFill || '#fff'"
}
},
"update": {"x": {"signal": "datum.bounds.x2"}}
}
},
{
"name": "handles-inner-arc",
"description": "the inner circle mark that serves as the the 'slider handle'",
"type": "arc",
"from": {"data": "handles-outer-arc"},
"interactive": false,
"encode": {
"enter": {
"y": {"signal": "datum.y"},
"innerRadius": {"value": 0},
"outerRadius": {"signal": "1"},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "configDateStepSlider.handle.innerStroke"
},
"fill": {
"signal": "configDateStepSlider.handle.innerFill || '#999'"
}
},
"update": {"x": {"signal": "datum.x"}}
}
}
]
},
{
"name": "text-date-unit-label",
"type": "text",
"from": {"data": "group-date-granularity-slider"},
"encode": {
"update": {
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"x": {"signal": "-ganttDimensions.width"},
"baseline": {"value": "middle"},
"text": {
"signal": "(!isValid(xCurrentSecondaryUnit) ? 'Year' : upper(slice(xCurrentSecondaryUnit, 0,1)) + slice(xCurrentSecondaryUnit, -length(xCurrentSecondaryUnit)+1)) + ' View'"
},
"fill": {"value": "#666"}
}
}
},
{
"name": "label-date-granularity-slider-text",
"description": "the title for the slider control",
"from": {"data": "group-date-granularity-slider"},
"type": "text",
"interactive": false,
"encode": {
"enter": {
"x": {"signal": "datum.bounds.x1"},
"dx": {
"signal": "-configDateStepSlider.track.cornerRadius*2"
}
},
"update": {
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {
"signal": "configDateStepSlider.label.text || ''"
},
"baseline": {"value": "middle"},
"font": {"signal": "configDateStepSlider.label.font"},
"fontSize": {
"signal": "configDateStepSlider.label.fontSize"
},
"fontStyle": {
"signal": "configDateStepSlider.label.fontStyle"
},
"align": {"value": "right"},
"fill": {"signal": "configDateStepSlider.label.fill"}
}
}
},
{
"name": "granularity-reset-interactive-rect",
"type": "rect",
"from": {"data": "group-granularity-reset"},
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1-5"},
"x2": {"signal": "datum.bounds.x2+5"},
"y": {"signal": "datum.bounds.y1-5"},
"y2": {"signal": "datum.bounds.y2+5"},
"fillOpacity": {"value": 0},
"cursor": {
"signal": "granularityResetMouseover ? 'pointer' : 'default'"
},
"tooltip": {
"signal": "configDateStepSlider.reset.tooltipText"
}
}
}
},
{
"name": "granularity-slider-interactive-rect",
"type": "rect",
"from": {"data": "group-date-granularity-slider"},
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1"},
"x2": {"signal": "datum.bounds.x2"},
"y": {"signal": "datum.bounds.y1-5"},
"y2": {"signal": "datum.bounds.y2+5"},
"fillOpacity": {"value": 0},
"cursor": {"value": "pointer"},
"tooltip": {
"signal": "granularitySliderMouseDown ? '' : 'Reset date granularity'"
}
}
}
}
]
},
{
"name": "gantt-horizontal-scroll-map-interactive-rect",
"from": {"data": "group-date-granularity"},
"type": "rect",
"encode": {
"update": {
"tooltip": {
"signal": "ganttHorizontalScrollMapMouseDown ? '' : 'Horizontal scroll'"
},
"x": {
"signal": "ganttHorizontalScrollMapMouseDown ? -ganttPosition.x : 0"
},
"y": {
"signal": "ganttHorizontalScrollMapMouseDown ? -ganttPosition.y-ganttPosition.dy : datum.bounds.y1 - configDateStepSlider.track.height*3"
},
"x2": {"signal": "panAndZoomMode ? 0 : datum.bounds.x2"},
"y2": {
"signal": "datum.bounds.y1 + (ganttHorizontalScrollMapMouseDown ? height : 0)"
},
"fillOpacity": {"value": 0},
"cursor": {
"signal": "ganttHorizontalScrollMapMouseOver ? 'pointer' : 'default'"
}
}
}
}
]
},
{
"name": "group-stroke-marks",
"type": "group",
"interactive": false,
"encode": {
"update": {
"width": {"signal": "ganttPosition.x+ganttDimensions.width"},
"y": {"signal": "-ganttPosition.dy*2"},
"height": {"signal": "ganttDimensions.height+ganttPosition.dy*2"}
}
},
"marks": [
{
"name": "outer-rect",
"interactive": false,
"type": "rect",
"encode": {
"update": {
"width": {"signal": "ganttPosition.x+ganttDimensions.width"},
"y": {"signal": "ganttPosition.dy*2"},
"height": {"signal": "ganttDimensions.height"},
"stroke": {"signal": "configBorders.stroke"},
"strokeWidth": {"signal": "configBorders.strokeWidth"}
}
}
},
{
"name": "column-header-left-stroke",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"x": {"value": -0.5},
"y": {"signal": "ganttPosition.dy"},
"width": {"signal": "configBorders.strokeWidth"},
"height": {"signal": "ganttPosition.dy"},
"fill": {"signal": "configBorders.stroke"}
}
}
},
{
"name": "column-gantt-divider-rect",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "ganttPosition.x-configBorders.strokeWidth/2"
},
"y": {
"signal": "isValid(xCurrentTopLevelUnit) ? 0 : ganttPosition.dy"
},
"width": {"signal": "configBorders.strokeWidth"},
"height": {
"signal": "((isValid(xCurrentTopLevelUnit) ? 2 : 1) * ganttPosition.dy)+ganttDimensions.height"
},
"fill": {"signal": "configBorders.stroke"}
}
}
},
{
"name": "gantt-end-top-secondary-axis-border",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"y": {
"signal": "ganttPosition.dy-configBorders.strokeWidth/2"
},
"width": {"signal": "ganttPosition.x+ganttDimensions.width"},
"height": {"signal": "configBorders.strokeWidth"},
"fill": {"signal": "configBorders.stroke"}
}
}
},
{
"name": "gantt-end-top-level-border",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"y": {"signal": "-configBorders.strokeWidth/2"},
"x": {"signal": "ganttPosition.x"},
"width": {"signal": "ganttDimensions.width"},
"height": {
"signal": "isValid(xCurrentTopLevelUnit) ? configBorders.strokeWidth : 0"
},
"fill": {"signal": "configBorders.stroke"}
}
}
},
{
"name": "gantt-end-top-level-tick",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"y": {
"signal": "isValid(xCurrentTopLevelUnit) ? 0 : ganttPosition.dy"
},
"x": {
"signal": "ganttPosition.x+ganttDimensions.width-configBorders.strokeWidth/2"
},
"width": {"signal": " configBorders.strokeWidth"},
"height": {
"signal": "ganttPosition.dy*(isValid(xCurrentTopLevelUnit) ? 2 : 1)"
},
"fill": {"signal": "configBorders.stroke"}
}
}
}
]
}
]
}
],
"scales": [
{
"name": "xScaleGanttTimeSeries",
"type": "time",
"clamp": true,
"domain": {"signal": "xDomain"},
"range": {"signal": "[0, ganttDimensions.width]"}
},
{
"name": "xScaleGanttTimeSeriesMax",
"type": "time",
"clamp": true,
"domain": {"signal": "maxXDomain"},
"range": {"signal": "[1, ganttDimensions.width-2]"}
},
{
"name": "xZoomScale0",
"type": "linear",
"domain": {"signal": "[0,1]"},
"range": {"signal": "[maxXDomain[0], minXDomain[0]]"},
"clamp": true
},
{
"name": "xZoomScale1",
"type": "linear",
"domain": {"signal": "[0,1]"},
"range": {"signal": "[maxXDomain[1], minXDomain[1]]"},
"clamp": true
},
{
"name": "xAnchorScale",
"type": "linear",
"domain": {"signal": "[0,1]"},
"range": {
"signal": "[maxXDomain[0]-span(xDomain)/2, maxXDomain[1]+span(xDomain)/2]"
},
"clamp": true
}
],
"data": [
{
"name": "dataset",
"url": "https://raw.githubusercontent.com/Giammaria/PublicFiles/master/data/20240511_hierarchical_gantt_dataset.json",
"format": {
"parse": {
"id": "number",
"parentId": "number",
"name": "string",
"startDate": "date",
"endDate": "date",
"decimalPercentComplete": "number",
"dependencyId": "string"
}
}
},
{
"name": "dataset-formatted",
"source": "dataset",
"transform": [
{
"type": "formula",
"expr": "timeFormat(datum.startDate, '%Y-%m-%dT%H:%M:%S.%LZ')",
"as": "startDateFormatted"
},
{
"type": "formula",
"expr": "timeFormat(datum.endDate, '%Y-%m-%dT%H:%M:%S.%LZ')",
"as": "endDateFormatted"
},
{
"type": "formula",
"expr": "isValid(datum.color) ? datum.color : configRow.defaultFill",
"as": "color"
},
{
"type": "formula",
"expr": "toDate(timeFormat(datum.startDate, '%Y-%m-%d'))",
"as": "startDate"
},
{
"type": "formula",
"expr": "toDate(timeFormat(datum.endDate, '%Y-%m-%d'))",
"as": "endDate"
},
{
"type": "formula",
"expr": "!isValid(datum['startDate']) ? null : datum['endDate']",
"as": "endDate"
},
{"type": "stratify", "key": "id", "parentKey": "parentId"},
{"type": "window", "ops": ["row_number"], "as": ["sort"]},
{
"type": "formula",
"expr": "{id: datum.id, parentId: datum.parentId}",
"as": "idObj"
},
{"type": "window", "ops": ["row_number"], "as": ["index"]}
]
},
{
"name": "dataset-extent",
"source": "dataset-formatted",
"transform": [
{
"type": "aggregate",
"fields": ["startDate", "endDate"],
"ops": ["min", "max"],
"as": ["minStart", "maxEnd"]
}
]
},
{
"name": "nodes-base",
"source": "dataset",
"transform": [
{"type": "formula", "expr": "datum.id", "as": "id"},
{
"type": "formula",
"expr": "datum.parentId==='null' ? null : datum.parentId",
"as": "parentId"
},
{
"type": "formula",
"expr": "datum.startDate==='null' ? null : toNumber(datum.startDate)",
"as": "start"
},
{
"type": "formula",
"expr": "datum.endDate==='null' ? null : toNumber(datum.endDate)",
"as": "end"
},
{
"type": "formula",
"expr": "datum.decimalPercentComplete==='null' ? null : toNumber(datum.decimalPercentComplete)",
"as": "percentComplete"
},
{
"type": "formula",
"expr": "datum.dependencyId && datum.dependencyId!=='null' ? split(replace(datum.dependencyId,' ',''), ',') : []",
"as": "dependencies"
},
{
"type": "formula",
"expr": "length(datum.dependencies)>0",
"as": "hasDependencies"
},
{"type": "formula", "expr": "!isValid(datum.parentId)", "as": "isRoot"},
{"type": "formula", "expr": "datum.name", "as": "label"},
{"type": "formula", "expr": "datum.color || '#40407d'", "as": "color"},
{"type": "window", "ops": ["row_number"], "as": ["orderAll"]}
]
},
{
"name": "tree-struct",
"source": "nodes-base",
"transform": [
{"type": "stratify", "key": "id", "parentKey": "parentId"},
{
"type": "tree",
"method": "tidy",
"size": [1, 1],
"sort": {"field": "orderAll"},
"as": ["x", "y", "depth", "children"]
},
{"type": "formula", "expr": "datum.depth", "as": "level"},
{"type": "formula", "expr": "datum.children > 0", "as": "hasChildren"}
]
},
{
"name": "preorder",
"source": "tree-struct",
"transform": [
{"type": "window", "ops": ["rank"], "as": ["index"]},
{
"type": "formula",
"expr": "configIncludeRoot ? datum.index : datum.index-1",
"as": "index"
},
{"type": "formula", "expr": "datum.index - 1", "as": "idx"},
{"type": "filter", "expr": "datum.index >= 0"}
]
},
{
"name": "preorder-for-desc",
"source": "preorder",
"transform": [
{
"type": "formula",
"expr": "datum.idx>clickedIdx && datum.depth<=clickedDepth ? 1 : 0",
"as": "breakFlag"
},
{
"type": "window",
"ops": ["sum"],
"fields": ["breakFlag"],
"sort": {"field": ["idx"], "order": ["ascending"]},
"as": ["cumeBreak"]
}
]
},
{
"name": "clicked-descendants",
"source": "preorder-for-desc",
"transform": [
{
"type": "filter",
"expr": "isValid(clickedNode) && datum.idx > clickedIdx && datum.depth > clickedDepth && datum.cumeBreak == baseBreak"
},
{"type": "project", "fields": ["id"]},
{"type": "formula", "expr": "true", "as": "isDescendantOfClicked"}
]
},
{
"name": "clicked-children",
"source": "preorder",
"transform": [
{
"type": "filter",
"expr": "isValid(clickedNode) && datum.parentId===clickedNode"
},
{"type": "project", "fields": ["id"]},
{"type": "formula", "expr": "true", "as": "isChildOfClicked"}
]
},
{
"name": "state-initial-expanded",
"source": "preorder",
"transform": [
{
"type": "formula",
"expr": "datum.depth < resetLevel",
"as": "expanded"
},
{"type": "project", "fields": ["id", "expanded"]}
]
},
{
"name": "visible-initial",
"source": "preorder",
"transform": [
{
"type": "lookup",
"from": "state-initial-expanded",
"key": "id",
"fields": ["id"],
"values": ["expanded"],
"as": ["expanded"]
},
{
"type": "lookup",
"from": "state-initial-expanded",
"key": "id",
"fields": ["parentId"],
"values": ["expanded"],
"as": ["parentExpanded"]
},
{
"type": "formula",
"expr": "(expandAllClicked ? datum.depth == configInitialDepth : datum.depth == resetLevel) ? true : false",
"as": "visible"
},
{
"type": "lookup",
"from": "preorder",
"key": "id",
"fields": ["parentId"],
"values": ["visible"],
"as": ["parentVisible"]
},
{
"type": "formula",
"expr": "(expandAllClicked ? datum.depth == configInitialDepth : datum.depth == resetLevel) ? true : (datum.parentVisible && datum.parentExpanded)",
"as": "visible"
},
{"type": "filter", "expr": "datum.visible"},
{"type": "window", "ops": ["row_number"], "as": ["indexBefore"]},
{"type": "window", "ops": ["row_number"], "as": ["index"]},
{"type": "formula", "expr": "datum.index-1", "as": "index"}
]
},
{
"name": "expand-events",
"values": [],
"on": [
{"trigger": "clickedNodeTick", "insert": "{ id: clickedNode, ts: now() }"},
{"trigger": "resetLevel", "remove": true}
]
},
{
"name": "click-counts",
"source": "expand-events",
"transform": [
{
"type": "aggregate",
"groupby": ["id"],
"ops": ["count"],
"as": ["clicks"]
},
{"type": "formula", "expr": "datum.clicks % 2 == 1", "as": "oddToggle"}
]
},
{
"name": "click-counts-prev",
"source": "click-counts",
"transform": [
{ "type": "formula", "expr": "datum.id===clickedNode ? max(datum.clicks-1,0) : datum.clicks", "as": "clicks" },
{ "type": "formula", "expr": "datum.clicks % 2 == 1", "as": "oddToggle" }
]
},
{
"name": "state-expanded-prev",
"source": "state-initial-expanded",
"transform": [
{ "type": "lookup", "from": "click-counts-prev", "key": "id", "fields": ["id"], "values": ["oddToggle"], "as": ["oddToggle"] },
{ "type": "formula", "expr": "datum.oddToggle ? !datum.expanded : datum.expanded", "as": "expanded" },
{ "type": "project", "fields": ["id","expanded"] }
]
},
{
"name": "visible-before",
"values": [],
"on": [
{ "trigger": "resetLevel", "remove": true },
{ "trigger": "resetLevel", "insert": "data('visible-initial')" },
{ "trigger": "snapshotNow", "remove": true },
{
"trigger": "snapshotNow",
"insert": "isArray(data('visible-after')) && length(data('visible-after'))>0 ? data('visible-after') : data('visible-initial')"
}
]
},
{
"name": "state-expanded",
"source": "state-initial-expanded",
"transform": [
{
"type": "lookup",
"from": "click-counts",
"key": "id",
"fields": ["id"],
"values": ["oddToggle"],
"as": ["oddToggle"]
},
{
"type": "formula",
"expr": "datum.oddToggle ? !datum.expanded : datum.expanded",
"as": "expanded"
},
{"type": "project", "fields": ["id", "expanded"]}
]
},
{
"name": "visible-before-flag",
"source": "visible-before",
"transform": [{"type": "formula", "expr": "true", "as": "isFromBefore"}]
},
{
"name": "visible-after",
"source": "preorder",
"transform": [
{
"type": "lookup",
"from": "state-expanded",
"key": "id",
"fields": ["id"],
"values": ["expanded"],
"as": ["expandedNow"]
},
{
"type": "lookup",
"from": "state-expanded",
"key": "id",
"fields": ["parentId"],
"values": ["expanded"],
"as": ["parentExpandedNow"]
},
{
"type": "lookup",
"from": "visible-before",
"key": "id",
"fields": ["id"],
"values": ["id"],
"as": ["wasId"]
},
{"type": "formula", "expr": "isValid(datum.wasId)", "as": "inBefore"},
{
"type": "lookup",
"from": "visible-before",
"key": "id",
"fields": ["parentId"],
"values": ["id"],
"as": ["parentWasId"]
},
{
"type": "formula",
"expr": "isValid(datum.parentWasId)",
"as": "parentVisiblePrev"
},
{
"type": "lookup",
"from": "clicked-children",
"key": "id",
"fields": ["id"],
"values": ["isChildOfClicked"],
"as": ["isChildOfClicked"]
},
{
"type": "lookup",
"from": "clicked-descendants",
"key": "id",
"fields": ["id"],
"values": ["isDescendantOfClicked"],
"as": ["isDescendantOfClicked"]
},
{
"type": "formula",
"expr": "datum.depth == configInitialDepth ? true : false",
"as": "visible"
},
{
"type": "formula",
"expr": "expandAllClicked ? true : (collapseAllClicked ? (datum.depth == configInitialDepth) : (expandCollapseClick.action === 'expand' ? (datum.inBefore || (datum.parentVisiblePrev && datum.parentExpandedNow && isValid(datum.isChildOfClicked) && datum.isChildOfClicked)) : (datum.inBefore && !(isValid(datum.isDescendantOfClicked) && datum.isDescendantOfClicked))))",
"as": "visible"
},
{"type": "filter", "expr": "datum.visible"},
{
"type": "lookup",
"from": "nodes-base",
"key": "id",
"fields": ["id"],
"values": ["orderAll"],
"as": ["orderAll"]
},
{
"type": "window",
"ops": ["rank"],
"as": ["indexAfter"],
"sort": {"field": ["orderAll"], "order": ["ascending"]}
}
]
},
{
"name": "rows-before",
"source": "visible-before",
"transform": [
{
"type": "lookup",
"from": "nodes-base",
"key": "id",
"fields": ["id"],
"values": ["orderAll", "start", "end", "label", "parentId"],
"as": ["orderAll", "start", "end", "label", "parentId"]
},
{
"type": "window",
"ops": ["rank"],
"as": ["index"],
"sort": {"field": ["orderAll"], "order": ["ascending"]}
}
]
},
{
"name": "rows-after",
"source": "visible-after",
"transform": [
{
"type": "lookup",
"from": "nodes-base",
"key": "id",
"fields": ["id"],
"values": ["orderAll", "start", "end"],
"as": ["orderAll", "start", "end"]
},
{
"type": "window",
"ops": ["rank"],
"as": ["indexAfter"],
"sort": {"field": ["orderAll"], "order": ["ascending"]}
}
]
},
{
"name": "render-rows",
"source": ["rows-before", "rows-after"],
"transform": [
{"type": "aggregate", "groupby": ["id"]},
{
"type": "lookup",
"from": "rows-before",
"key": "id",
"fields": ["id"],
"values": ["index", "start", "end", "parentId"],
"as": ["indexBefore", "startBefore", "endBefore", "parentId"]
},
{
"type": "lookup",
"from": "rows-after",
"key": "id",
"fields": ["id"],
"values": ["indexAfter", "start", "end"],
"as": ["indexAfter", "startAfter", "endAfter"]
},
{
"type": "lookup",
"from": "tree-struct",
"key": "id",
"fields": ["id"],
"values": ["name", "hasChildren", "depth", "color"],
"as": ["name", "hasChildren", "depth", "color"]
},
{
"type": "formula",
"expr": "isValid(datum.indexBefore)",
"as": "inBefore"
},
{
"type": "formula",
"expr": "isValid(datum.indexAfter)",
"as": "inAfter"
},
{
"type": "formula",
"expr": "indexof(rowsBeforeIds, clickedNode) >= 0 ? data('rows-before')[indexof(rowsBeforeIds, clickedNode)].index : (indexof(pluck(data('rows-after'),'id'), clickedNode) >= 0 ? data('rows-after')[indexof(pluck(data('rows-after'),'id'), clickedNode)].index : 1)",
"as": "clickedIndex"
},
{
"type": "formula",
"expr": "isValid(expandCollapseClick.nodeId) ? (datum.inBefore && datum.inAfter ? datum.indexBefore : (!datum.inBefore && datum.inAfter ? datum.clickedIndex : (datum.inBefore && !datum.inAfter ? datum.indexBefore : (isValid(datum.indexBefore) ? datum.indexBefore : (isValid(datum.indexAfter) ? datum.indexAfter : 1))))) : (isValid(datum.indexBefore) ? datum.indexBefore : (isValid(datum.indexAfter) ? datum.indexAfter : 1))",
"as": "idxStart"
},
{
"type": "formula",
"expr": "isValid(expandCollapseClick.nodeId) ? (datum.inBefore && datum.inAfter ? datum.indexAfter : (!datum.inBefore && datum.inAfter ? datum.indexAfter : (datum.inBefore && !datum.inAfter ? datum.clickedIndex : (isValid(datum.indexAfter) ? datum.indexAfter : (isValid(datum.indexBefore) ? datum.indexBefore : 1))))) : (isValid(datum.indexAfter) ? datum.indexAfter : (isValid(datum.indexBefore) ? datum.indexBefore : 1))",
"as": "idxEnd"
},
{
"type": "formula",
"expr": "configRow.rowHeight * ( (1 - expandCollapseAnimTEasedSafe)*(datum.idxStart - 1) + expandCollapseAnimTEasedSafe*(datum.idxEnd - 1) )",
"as": "y"
},
{
"type": "formula",
"expr": "configRow.rowHeight * ( (1 - expandCollapseAnimTEasedSafe)* datum.idxStart + expandCollapseAnimTEasedSafe* datum.idxEnd )",
"as": "y2"
},
{
"type": "formula",
"expr": "datum.y+configRow.rowHeight*0.25",
"as": "yWithPadding"
},
{
"type": "formula",
"expr": "datum.y2-configRow.rowHeight*0.25",
"as": "y2WithPadding"
},
{
"type": "formula",
"expr": "datum.inBefore && datum.inAfter ? 1 : (!datum.inBefore && datum.inAfter ? expandCollapseAnimTEasedSafe : (datum.inBefore && !datum.inAfter ? 1 - expandCollapseAnimTEasedSafe : 0))",
"as": "opacity"
},
{
"type": "formula",
"expr": "isValid(datum.startBefore) ? datum.startBefore : datum.startAfter",
"as": "startAny"
},
{
"type": "formula",
"expr": "isValid(datum.endBefore) ? datum.endBefore : datum.endAfter",
"as": "endAny"
},
{
"type": "formula",
"expr": "scale('xScaleGanttTimeSeries', datum.startAny)",
"as": "x"
},
{
"type": "formula",
"expr": "scale('xScaleGanttTimeSeries', datum.endAny)",
"as": "x2"
}
]
},
{
"name": "debug-expanded-row",
"source": "state-expanded",
"transform": [{"type": "filter", "expr": "datum.id === clickedNode"}]
},
{
"name": "height",
"source": "visible-after",
"transform": [
{"type": "aggregate", "ops": ["count"], "as": ["height"]},
{
"type": "formula",
"expr": "(datum.height)*configRow.rowHeight",
"as": "height"
}
]
},
{
"name": "height-animation",
"values": [{"height": null, "timestamp": null}],
"on": [
{
"trigger": "resetLevel",
"insert": "{height: safeHeight, timestamp: now()}",
"remove": false
},
{
"trigger": "clickedNode",
"insert": "{height: safeHeight, timestamp: now()}",
"remove": false
}
],
"transform": [
{
"type": "formula",
"expr": "isValid(datum.height) ? datum.height : safeHeight",
"as": "height"
},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "timestamp", "order": "descending"},
"as": ["index"]
},
{
"type": "window",
"ops": ["lead"],
"fields": ["height"],
"sort": {"field": "index"},
"as": ["previousHeight"]
},
{"type": "filter", "expr": "(datum.height !== datum.previousHeight)"},
{
"type": "formula",
"expr": "datum.previousHeight || datum.height",
"as": "previousHeight"
},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "index", "order": "ascending"},
"as": ["index"]
},
{"type": "filter", "expr": "datum.index === 1"},
{"type": "formula", "expr": "datum.timestamp || now()", "as": "start"},
{
"type": "formula",
"expr": "datum.start + configAnimationDuration.nodeExpandCollapse",
"as": "end"
},
{
"type": "formula",
"expr": "timer <= datum.end ? clamp((timer-datum.start)/(datum.end-datum.start), 0,1) : 1",
"as": "t"
},
{
"type": "formula",
"expr": "datum.t < 0.5 ? 4 * pow(datum.t, 3) : 1 - pow(-2 * datum.t + 2, 3) / 2",
"as": "tEased"
},
{
"type": "formula",
"expr": "datum.previousHeight+(datum.height-datum.previousHeight)*datum.tEased",
"as": "animatedHeight"
}
]
},
{
"name": "ganttTimeSeriesConfigurations",
"values": [{"timeSeriesWidths": 1}],
"transform": [
{
"type": "formula",
"expr": "{year: span(xDomain)/msPerDay/365, month: span(xDomain)/msPerDay/12, day: span(xDomain)/msPerDay, hour: span(xDomain)/msPerDay*24}",
"as": "timeSeriesWidths"
},
{
"type": "formula",
"expr": "{year: ceil(datum.timeSeriesWidths.year*smallestAllowableXWidth), month: ceil(datum.timeSeriesWidths.month*smallestAllowableXWidth), day: ceil(datum.timeSeriesWidths.day*smallestAllowableXWidth), hour: ceil(datum.timeSeriesWidths.hour*smallestAllowableXWidth*2)}",
"as": "timeSeriesWidths"
},
{
"type": "formula",
"expr": "datum.timeSeriesWidths.hour <= ganttDimensions.width ? 'hour' : datum.timeSeriesWidths.day <= ganttDimensions.width ? 'day' : datum.timeSeriesWidths.month <= ganttDimensions.width ? 'month' : 'year'",
"as": "secondaryTickCount"
},
{
"type": "formula",
"expr": "['%Y', '%b', '%d', '%I %p'][indexof(['year', 'month', 'day', 'hour'], datum.secondaryTickCount)]",
"as": "secondaryFormat"
},
{
"type": "formula",
"expr": "['year', 'month', 'day', 'hour'][indexof(['year', 'month', 'day', 'hour'], datum.secondaryTickCount)-1]",
"as": "tickCount"
},
{
"type": "formula",
"expr": "['%Y', '%b %Y', '%d-%b-%y', '%I %p'][indexof(['year', 'month', 'day', 'hour'], datum.secondaryTickCount)-1]",
"as": "format"
}
]
},
{
"name": "expandAllCollapseAllConfig",
"values": [
{
"rowNumber": 0,
"name": "expandAll",
"path": "M342.6 534.6C330.1 547.1 309.8 547.1 297.3 534.6L137.3 374.6C124.8 362.1 124.8 341.8 137.3 329.3C149.8 316.8 170.1 316.8 182.6 329.3L320 466.7L457.4 329.4C469.9 316.9 490.2 316.9 502.7 329.4C515.2 341.9 515.2 362.2 502.7 374.7L342.7 534.7zM502.6 182.6L342.6 342.6C330.1 355.1 309.8 355.1 297.3 342.6L137.3 182.6C124.8 170.1 124.8 149.8 137.3 137.3C149.8 124.8 170.1 124.8 182.6 137.3L320 274.7L457.4 137.4C469.9 124.9 490.2 124.9 502.7 137.4C515.2 149.9 515.2 170.2 502.7 182.7z",
"size": 0.00155,
"fontWeight": 400,
"tooltip": "Expand all"
},
{
"rowNumber": 1,
"name": "collapseAll",
"path": "M342.6 105.4C330.1 92.9 309.8 92.9 297.3 105.4L137.3 265.4C124.8 277.9 124.8 298.2 137.3 310.7C149.8 323.2 170.1 323.2 182.6 310.7L320 173.3L457.4 310.6C469.9 323.1 490.2 323.1 502.7 310.6C515.2 298.1 515.2 277.8 502.7 265.3L342.7 105.3zM502.6 457.4L342.6 297.4C330.1 284.9 309.8 284.9 297.3 297.4L137.3 457.4C124.8 469.9 124.8 490.2 137.3 502.7C149.8 515.2 170.1 515.2 182.6 502.7L320 365.3L457.4 502.6C469.9 515.1 490.2 515.1 502.7 502.6C515.2 490.1 515.2 469.8 502.7 457.3z",
"size": 0.00155,
"tooltip": "Collapse all"
}
]
},
{
"name": "columnData",
"values": [
{
"name": "name",
"field": "name",
"type": "text",
"label": "Task",
"align": "left",
"allowableWidth": 120,
"alwaysShow": true
},
{
"name": "startDate",
"field": "startDate",
"type": "text",
"label": "Start Date",
"align": "right",
"allowableWidth": 55,
"alwaysShow": false
},
{
"name": "endDate",
"field": "endDate",
"type": "text",
"label": "End Date",
"align": "right",
"allowableWidth": 55,
"alwaysShow": false
},
{
"name": "duration",
"field": null,
"type": "text",
"label": "Duration",
"align": "right",
"allowableWidth": 45,
"alwaysShow": false
},
{
"name": "progress",
"field": null,
"type": "percentage",
"label": "Progress",
"align": "center",
"allowableWidth": 56,
"alwaysShow": false
}
],
"transform": [
{"type": "window", "ops": ["row_number"], "as": ["index"]},
{
"type": "window",
"ops": ["sum"],
"fields": ["allowableWidth"],
"sort": {"field": "index", "order": "ascending"},
"frame": [null, 0],
"as": ["x"]
},
{
"type": "formula",
"expr": "datum.x+(datum.align === 'right' ? datum.allowableWidth : datum.align === 'center' ? datum.allowableWidth/2 : 0)",
"as": "x"
},
{
"type": "formula",
"expr": "(datum.x-datum.allowableWidth)+(configColumn.innerPadding*(datum.index-1))",
"as": "x"
},
{
"type": "formula",
"expr": "showDetails ? 'show' : (datum.alwaysShow ? 'show' : 'hide')",
"as": "show"
},
{
"type": "formula",
"expr": "datum.show === 'show' ? datum.x : 0",
"as": "showX"
},
{
"type": "window",
"ops": ["last_value", "max"],
"fields": ["allowableWidth", "showX"],
"sort": {"field": "index"},
"frame": [null, null],
"as": ["lastAllowableWidth", "lastX"]
},
{
"type": "formula",
"expr": "(showDetails ? datum.lastAllowableWidth : datum.allowableWidth)+datum.lastX",
"as": "totalWidth"
}
]
}
],
"config": {"text": {"font": "Segoe UI"}}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment