Skip to content

Instantly share code, notes, and snippets.

@Giammaria
Last active December 30, 2025 15:05
Show Gist options
  • Select an option

  • Save Giammaria/f1b07a1bb669783b0648cbf91627674d to your computer and use it in GitHub Desktop.

Select an option

Save Giammaria/f1b07a1bb669783b0648cbf91627674d to your computer and use it in GitHub Desktop.
20251230_hierarchical_gantt_v_v2.8
{
"$schema": "https://vega.github.io/schema/vega/v6.json",
"usermeta": {
"version": "02.02",
"developedBy": "Madison Giammaria",
"gitHub": "https://github.com/Giammaria",
"linkedIn": "https://www.linkedin.com/in/madison-giammaria-58463b33",
"email": "giammariam@gmail.com",
"visualName": "Hierarchical Gantt (v2)",
"visualDescription": "This Hierarchical Gantt is a production-ready, expand/collapse timeline visual built in Vega that works in Power BI (Deneb) or any web page via vega-embed. It lets report authors explore complex work breakdown structures the way people actually think: parents with nested children, sorted at the sibling level (so hierarchy is never broken), with smooth animations that preserve context as you sort, expand, and collapse. Out of the box it includes optional dependency lines, optional bar labels, a details panel with configurable columns, and intuitive controls for pan/zoom and scrolling, and the best part is that adding new columns or tooltip fields is mostly “data-driven” configuration rather than rewriting Vega, making it approachable for teams who want a modern, interactive Gantt without building a custom web app."
},
"bounds": "flush",
"autosize": {"resize": true, "type": "pad", "contains": "padding"},
"padding": 0,
"background": "#fff",
"signals": [
{
"name": "configIsPowerBIVisual",
"description": "Indicates whether the spec is running as a Power BI visual; used for sizing/autosize decisions. Constant false in this spec.",
"value": false
},
{
"name": "configDesiredGanttHeight",
"description": "Requested/target Gantt height before autosize/host constraints are applied. Constant 400.",
"update": "400"
},
{
"name": "configDesiredWidth",
"description": "Requested/target view width before autosize/host constraints are applied. Constant 1300.",
"update": "1300"
},
{
"name": "configColumn",
"description": "Configuration object for the left-hand (non-Gantt) columns (layout, widths, behavior).",
"init": "{xOffset: 5, innerPadding: 25, values: {fontWeight: 400, fontSize: 12}}"
},
{
"name": "configGantt",
"description": "Configuration object for the Gantt region (layout + sizing rules). Computed via update from layout-related signals (e.g., ganttDimensions/width).",
"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: '#000', strokeWidth: 0.5, strokeDash: [3,5], strokeDashOffset: 4 }}"
},
{
"name": "configBorders",
"description": "Configuration for borders (stroke/width/visibility).",
"init": "{'stroke': '#ccc', strokeWidth: 1.5}"
},
{
"name": "configGanttGridlines",
"description": "Configuration for Gantt gridlines (visibility, styling, cadence).",
"init": "{'topAxis': {'stroke': '#666', strokeWidth: 1}, secondaryAxis: {'stroke': '#ddd', strokeWidth: 1.5}}"
},
{
"name": "configAnimationDuration",
"description": "Controls animation durations (ms) used for various transitions.",
"init": "{granularityReset: 750, granularitySliderClick: 250, anchorReset: 750, anchorMapClick: 250, showDetails: 750, nodeExpandCollapse: 500, sort: 750, settingsMenu: 250}"
},
{
"name": "configExpandCollapseButtons",
"description": "Configuration for top controls/buttons (expand/collapse all).",
"init": "{padding: 5, yOffset: 5, label: {text: 'Expand/Collapse All', font: 'Segoe UI', fontSize: 12, fill: '#888', fontStyle: 'regular', dy: 15}, outerStroke: '#767', fill: '#EEE', hoverFill: 'steelblue'}"
},
{
"name": "configShowDetails",
"description": "Configuration for the 'Show Details' control (position, enabled state, sizing). Computed via update (depends on width).",
"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 Detail Columns', 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": "Configuration for pan/zoom mode control (UI layout + toggle behavior). Computed via update from layout signals (height/width and related control placements).",
"update": "{enabled: true, initialValue: false, xOffset: 0, track: {height: 7.5, width: 25, cornerRadius: 5, fill: '#EEE', stroke: '#777', strokeWidth: 1}, handle: {stroke: '#777', strokeWidth: 1, fill: '#fff'}, label: {text: 'Pan & Zoom Mode', font: 'Segoe UI', fontSize: 12, fill: '#888', fontStyle: 'regular', dx: 10}, on: {fill: '#d1e0ec', fillOpacity: 1, stroke: '#777', strokeWidth: 1}, tooltip: {object: {title: 'Mouse Wheel Scroll vs. Pan & Zoom', '• ‎ ‎ ‎ ‎ ‎ ‎': 'Click here to toggle between vertical scrolling and toggle granularity zooming + panning.'}}}"
},
{
"name": "configShowBarLabels",
"description": "Configuration for the 'Show Bar Labels' control (UI placement/sizing). Computed via update (depends on width).",
"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 Bar Labels', font: 'Segoe UI', fontSize: 12, fill: '#888', fontStyle: 'regular', dx: 10}, on: {fill: '#d1e0ec', fillOpacity: 1, stroke: '#777', strokeWidth: 1}, tooltip: {text: 'Show bar labels on the Gantt'}}"
},
{
"name": "configShowDependencies",
"description": "Configuration for the 'Show Dependencies' control (UI placement/sizing). Computed via update (depends on width).",
"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 Dependency Lines', font: 'Segoe UI', fontSize: 12, fill: '#888', fontStyle: 'regular', dx: 10}, on: {fill: '#d1e0ec', fillOpacity: 1, stroke: '#777', strokeWidth: 1}, tooltip: {text: 'Show dependency lines on the Gantt'}}"
},
{
"name": "configDateGranularitySlider",
"description": "Configuration for the date granularity slider control (UI placement/sizing). Computed via update (depends on width).",
"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 granularity', fill:'#888', hoverFill: '#333'}}"
},
{
"name": "configRow",
"description": "Configuration for task rows.",
"init": "{rowHeight: 25, levelIndentWidth: 15, defaultFill: '#40407d'}"
},
{
"name": "configIncludeRoot",
"description": "Whether the synthetic/root node is included in the visible hierarchy.",
"init": "false"
},
{
"name": "configInitialDepth",
"description": "Initial expansion depth (levels) that should start expanded. Collapse All also reverts to this level. Default 1.",
"value": 1
},
{
"name": "configVerticalScrollbar",
"description": "Configuration for the vertical scrollbar (enabled state, track/window sizes, placement). Computed via update from actualHeight and adjustedHeight.",
"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": "expandAllClicked",
"description": "Tracks whether the expand-all button was the most recent button-click intent (used to manage hover/click state/visual feedback). Updated by expand/collapse button events and cleared by row hover events.",
"init": "false",
"on": [
{
"events": "@expand-all-collapse-all-button-background:click{0,50}",
"update": "isAnimating ? expandAllClicked : !isValid(datum.datum) ? expandAllClicked : datum.datum.name === 'expandAll' ? true : false"
},
{
"events": "@gantt-row-clickable-rect:mousemove, @column-row-background-rect:mousemove",
"update": "!isAnimating ? false : expandAllClicked"
}
]
},
{
"name": "collapseAllClicked",
"description": "Tracks whether the collapse-all button was the most recent button-click intent (used to manage hover/click state/visual feedback). Updated by expand/collapse button events and cleared by row hover events.",
"init": "false",
"on": [
{
"events": "@expand-all-collapse-all-button-background:click",
"update": "isAnimating ? collapseAllClicked : !isValid(datum.datum) ? collapseAllClicked : datum.datum.name === 'collapseAll' ? true : false"
},
{
"events": "@gantt-row-clickable-rect:mousemove, @column-row-background-rect:mousemove",
"update": "!isAnimating ? false : collapseAllClicked"
}
]
},
{
"name": "preLastClickedNodeTargets",
"description": "Snapshot of the current visible target rows prior to an interaction (used as the 'source' state for animations, especially sort/expand/collapse). Updated on row/cell pointerdown and column-header pointerdown.",
"on": [
{
"events": "@gantt-row-clickable-rect:pointerdown, @column-row-background-rect:pointerdown",
"update": "rowAnimation.t === 1 ? data('hierarchy-visible-target') : preLastClickedNodeTargets"
},
{
"events": "@column-labels-interactive-rect:pointerdown",
"update": "!isAnimating ? data('hierarchy-visible-target') : preLastClickedNodeTargets"
},
{
"events": "@expand-all-collapse-all-button-background:mousein{0, 0}",
"update": "!isAnimating ? null : preLastClickedNodeTargets"
}
]
},
{
"name": "lastClickedNode",
"description": "Stores the last expanded/collapsed node context (id/state/datum) for driving interaction history/animation bookkeeping. Updated on row/cell interactions and cleared/reset on header interactions.",
"init": "null",
"on": [
{
"events": "@gantt-row-clickable-rect:pointerup, @column-row-background-rect:pointerup",
"update": "!isAnimating && isValid(datum) && datum.hasChildren && datum.level >= configInitialDepth ? {timestamp: now(), datum: datum, state: datum.isExpanded} : lastClickedNode"
},
{
"events": "@expand-all-collapse-all-button-background:mousemove{0, 0}",
"update": "!isAnimating ? null : lastClickedNode"
},
{
"events": "@gantt-row-clickable-rect:pointerdown, @column-row-background-rect:pointerdown",
"update": "!isAnimating && isValid(datum) && datum.hasChildren && datum.level >= configInitialDepth ? null : lastClickedNode"
},
{
"events": "@column-labels-interactive-rect:pointerdown",
"update": "null"
}
]
},
{
"name": "collapsedIds",
"description": "List of node ids that are considered collapsed under current rules/state. Computed from hierarchy state (reads hierarchy-initial).",
"update": "pluck(data('hierarchy-current-parent-collapsed-candidates'), 'id')"
},
{
"name": "expandedIds",
"description": "List of node ids that are considered expanded under current rules/state. Computed from hierarchy state (reads hierarchy-initial).",
"update": "pluck(data('hierarchy-current-parent-expanded-candidates'), 'id')"
},
{
"name": "mouseoverNodeDatum",
"description": "The datum currently under the mouse (row/cell hover) for hover styling/tooltips. Updated on row/cell mousemove events.",
"init": "null",
"on": [
{
"events": "@gantt-row-clickable-rect:mouseover, @gantt-row-milestone-clickable-rect:mouseover, @column-row-background-rect: mouseover",
"update": "isValid(datum) ? datum : null"
},
{
"events": "@gantt-row-clickable-rect:mouseout, @gantt-row-milestone-clickable-rect:mouseout, @column-row-background-rect:mouseout",
"update": "null"
}
]
},
{
"name": "interactionTypeHistory",
"description": "Short history stack of interaction types (e.g., nodeClick, button, sort) used to gate logic and choose animation behavior. Updated on pointerdown for rows/cells/buttons/column headers.",
"init": "[]",
"on": [
{
"events": "@gantt-row-clickable-rect:pointerdown, @column-row-background-rect:pointerdown",
"update": "!datum.hasChildren || datum.level < configInitialDepth ? interactionTypeHistory : length(interactionTypeHistory) === 0 ? ['nodeClick'] : split('nodeClick,' + join(interactionTypeHistory), ',',2)"
},
{
"events": "@expand-all-collapse-all-button-background:pointerdown",
"update": "datum.datum.name === 'collapseAll' ? length(interactionTypeHistory) === 0 ? ['collapseAll'] : split(('collapseAll,'+join(interactionTypeHistory)),',',2) : length(interactionTypeHistory) === 0 ? ['expandAll'] : split(('expandAll,')+join(interactionTypeHistory),',',2)"
},
{
"events": "@column-labels-interactive-rect:click",
"update": "length(interactionTypeHistory) === 0 ? ['sort'] : split(('sort,'+join(interactionTypeHistory)),',',2)"
}
]
},
{
"name": "expansionHistory",
"description": "Short history stack describing expansion intent/state changes used for gating transitions and preserving behavior across interactions. Updated on pointerdown for rows/cells/buttons.",
"init": "[]",
"on": [
{
"events": "@gantt-row-clickable-rect:pointerdown, @column-row-background-rect:pointerdown",
"update": "!datum.hasChildren || datum.level < configInitialDepth ? expansionHistory : length(expansionHistory) === 0 ? ['nodeClick'] : split('nodeClick,' + join(expansionHistory), ',',2)"
},
{
"events": "@expand-all-collapse-all-button-background:pointerdown",
"update": "datum.datum.name === 'collapseAll' ? length(expansionHistory) === 0 ? ['collapseAll'] : split(('collapseAll,'+join(expansionHistory)),',',2) : length(expansionHistory) === 0 ? ['expandAll'] : split(('expandAll,')+join(expansionHistory),',',2)"
}
]
},
{
"name": "expandedNodeIds",
"description": "Persistent expansion state store (array of expanded node ids as strings) so expansion does not reset on sort/recompute. Updated on row/cell pointerup and expand/collapse-all control.",
"init": "[]",
"on": [
{
"events": "@gantt-row-clickable-rect:pointerup, @column-row-background-rect:pointerup",
"update": "!isAnimating && isValid(datum) && datum.hasChildren && datum.level >= configInitialDepth ? (indexof(expandedNodeIds, toString(datum.id)) < 0 ? (length(expandedNodeIds) === 0 ? [toString(datum.id)] : split(join(expandedNodeIds, ',') + ',' + toString(datum.id), ',', 10000)) : (replace(replace(replace(expandedNodeIdsCSV, ',' + toString(datum.id) + ',', ','), /,,+/g, ','), /^,|,$/g, '') === '' ? [] : split(replace(replace(replace(expandedNodeIdsCSV, ',' + toString(datum.id) + ',', ','), /,,+/g, ','), /^,|,$/g, ''), ',', 10000))) : expandedNodeIds"
},
{
"events": "@expand-all-collapse-all-button-background:pointerdown",
"update": "!isAnimating && isValid(datum.datum) && datum.datum.name === 'expandAll' ? pluck(data('expandable-node-ids'), 'idStr') : (!isAnimating && isValid(datum.datum) && datum.datum.name === 'collapseAll' ? [] : expandedNodeIds)"
}
]
},
{
"name": "expandedNodeIdsCSV",
"description": "CSV-wrapped string representation of expandedNodeIds used for fast membership checks and array mutation helpers. Computed from expandedNodeIds.",
"update": "',' + join(expandedNodeIds, ',') + ','"
},
{
"name": "isInitial",
"description": "Indicates whether the visualization is still in its initial interaction phase (used to apply initial-depth defaults vs preserving user state). Updated on first row/cell pointerdown.",
"init": "true",
"on": [
{
"events": {"type": "timer"},
"update": "!isAnimating ? false : isInitial"
}
]
},
{
"name": "expandCollapseCount",
"description": "Counter incremented for expand/collapse actions (node toggles + expand/collapse-all). Used to trigger dependent updates/animations.",
"value": 0,
"on": [
{
"events": "@expand-all-collapse-all-button-background:click",
"update": "isAnimating ? expandCollapseCount : expandCollapseCount+(rowAnimationTEased === 1 ? 1 : 0)"
}
]
},
{
"name": "expandCollapseButtonDatum",
"description": "Holds the current expand/collapse-all button datum being interacted with (expandAll vs collapseAll) for hover/click rendering and logic. Set on button click and cleared on mouseout.",
"value": null,
"on": [
{
"events": "@expand-all-collapse-all-button-background:click",
"update": "!isAnimating ? datum.datum : expandCollapseButtonDatum"
},
{
"events": "@expand-all-collapse-all-button-background:mouseout",
"update": "!isAnimating ? null : expandCollapseButtonDatum"
}
]
},
{
"name": "xDomainZoomPercentage",
"description": "Zoom factor applied to the x-domain (time range). Updated by wheel zoom and slider/controls (reset/dblclick) to change time-scale granularity.",
"value": 0.5,
"on": [
{
"events": {
"markname": "granularity-slider-interactive-rect",
"source": "scope",
"type": "pointermove",
"consume": true,
"throttle": 80,
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "ganttHorizontalScrollMapMouseDown ? xDomainZoomPercentage : clamp(x('granularity-slider-interactive-rect')/configDateGranularitySlider.track.width,0,1)"
},
{
"events": {"signal": "granularityAnimation"},
"update": "granularityAnimation.active ? zoomStart + (zoomTarget - zoomStart) * granularityAnimation.tEased : xDomainZoomPercentage"
},
{
"events": {"signal": "wheel"},
"update": "ctrlKey || panAndZoomMode ? clamp(xDomainZoomPercentage * pow(wheel, pow(span(xDomain)/span(maxXDomain),0.9)), 0.005, 1) : xDomainZoomPercentage"
}
]
},
{
"name": "panning",
"description": "Whether a pan gesture is currently active in the Gantt region. Set on background pointerdown and cleared on pointerup.",
"init": "false",
"on": [
{
"events": "@rect-gantt-background:pointerdown",
"update": "panAndZoomMode"
},
{"events": "@rect-gantt-background:pointerup", "update": "false"}
]
},
{
"name": "panStartX",
"description": "Starting x position of the pointer when a pan begins (used to compute delta). Set on pan start.",
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background"
},
"update": "x(group())"
}
]
},
{
"name": "panStartAnchor",
"description": "Anchor x-domain position at pan start (used with panStartX to compute new domain). Set on pan start.",
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background"
},
"update": "xDomainAnchorPercentage"
}
]
},
{
"name": "anchorMin",
"description": "Minimum allowable x-domain anchor bounds derived from initial domain (prevents panning outside data extent). Computed from xDomainInitial.",
"update": "span(xDomain) / (2 * span(maxXDomain))"
},
{
"name": "anchorMax",
"description": "Maximum allowable x-domain anchor bounds derived from initial domain (prevents panning outside data extent). Computed from xDomainInitial.",
"update": "1 - anchorMin"
},
{
"name": "xDomainAnchorPercentage",
"description": "Anchor position for horizontal scroll-map (as percentage), used to pan the x-domain via mini-map controls. Updated via scroll-map hover/click and reset-anchor actions.",
"value": 0.5,
"on": [
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "rect-gantt-background",
"throttle": 50,
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "panAndZoomMode ? clamp(panStartAnchor + (panStartX - x(group())) / max(ganttDimensions.width,1), anchorMin, anchorMax) : xDomainAnchorPercentage"
},
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "gantt-horizontal-scroll-map-interactive-rect",
"throttle": 80,
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "granularitySliderMouseDown ? xDomainAnchorPercentage : clamp(((x(group())-ganttPosition.x)/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": "verticalScrollbarMouseDown",
"description": "Whether the vertical scrollbar thumb is currently being dragged. Set on scrollbar pointerdown and cleared on pointerup.",
"value": false,
"on": [
{
"events": "@vertical-scrollbar-group:pointerdown",
"update": "!panAndZoomMode && configVerticalScrollbar.enabled"
},
{
"events": {
"type": "mouseout",
"scope": "view",
"markname": "vertical-scrollbar-group",
"filter": ["!event.pointerdown"]
},
"update": "false"
},
{
"events": {
"type": "mouseover",
"scope": "scope",
"markname": "vertical-scrollbar-group",
"filter": ["event.pointerdown"]
},
"update": "!panAndZoomMode"
},
{"events": {"type": "pointerup"}, "update": "false"}
]
},
{
"name": "verticalScrollbarMouseOver",
"description": "Whether the mouse is hovering over the vertical scrollbar (for hover styling). Updated by scrollbar mouse events.",
"value": false,
"on": [
{
"events": "@vertical-scrollbar-group:mouseover",
"update": "!panAndZoomMode && configVerticalScrollbar.enabled"
},
{"events": "@vertical-scrollbar-group:mouseout", "update": "false"}
]
},
{
"name": "verticalScrollPercentage",
"description": "Current vertical scroll position as a percentage of total scrollable height. Updated by wheel scrolling, keyboard arrows, scrollbar dragging, and internal clamping logic.",
"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.0015 * adjustedHeight / actualHeight), 0, (actualHeight-adjustedHeight)/actualHeight)"
},
{
"events": "window:keydown[event.key === 'ArrowUp' || event.key === 'ArrowDown']{0,0}",
"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": 25,
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "panAndZoomMode && (actualHeight-adjustedHeight)>1 && isValid(yPanStart) ? clamp(yPanPrevious + ((yPanStart - y(group()))/max(actualHeight,1))*1, 0, (actualHeight-adjustedHeight)/actualHeight) : verticalScrollPercentage"
},
{
"events": {
"type": "pointermove",
"source": "scope",
"markname": "vertical-scrollbar-group",
"throttle": 50,
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "panAndZoomMode ? verticalScrollPercentage : !configVerticalScrollbar.enabled ? 0 : invert('scaleScrollHandleY', y(group()))"
},
{
"events": {"signal": "!configVerticalScrollbar.enabled"},
"update": "0"
},
{
"events": {"signal": "verticalScrollPercentage"},
"update": "isFinite(verticalScrollPercentage) ? verticalScrollPercentage : 0"
}
]
},
{
"name": "verticalScrollIncrement",
"description": "Temporary velocity/increment signal for keyboard-driven vertical scrolling (ArrowUp/ArrowDown). Updated on keydown/keyup.",
"update": "0.1 * (actualHeight-adjustedHeight)/actualHeight"
},
{
"name": "yPanStart",
"description": "Starting y position for vertical drag (background or scrollbar) used to compute scroll delta. Set on pointerdown and cleared on pointerup.",
"value": null,
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background",
"throttle": 20
},
"update": "y(group())"
}
]
},
{
"name": "yPanPrevious",
"description": "Previous y value during vertical drag to compute per-frame deltas. Updated during dragging.",
"value": null,
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background"
},
"update": "verticalScrollPercentage"
}
]
},
{
"name": "verticalScrollVelocity",
"description": "Scroll velocity accumulator used to smooth wheel/drag scrolling and apply decays over timer ticks. Updated by wheel and timer.",
"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": "Whether the horizontal scroll-map window is being dragged (mini-map panning). Set on scroll-map pointerdown and cleared on pointerup.",
"value": false,
"on": [
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:pointerdown",
"update": "true"
},
{"events": {"type": "pointerup"}, "update": "false"}
]
},
{
"name": "horizontalScrollMapHeight",
"description": "Computed height reserved for the horizontal scroll-map region based on which UI controls are enabled/visible.",
"value": 10
},
{
"name": "xDomainInitialWithoutPadding",
"description": "Initial x-domain derived from dataset time extents without padding. Computed by reading dataset-formatted.",
"init": "[data('dataset-extent')[0].minStart, data('dataset-extent')[0].maxEnd]"
},
{
"name": "xDomainInitial",
"description": "Initial x-domain including computed padding on both sides. Derived from xDomainInitialWithoutPadding and padding rules.",
"update": "[ xDomainInitialWithoutPadding[0] - max(xInitPadPerSide*span(xDomainInitialWithoutPadding), xInitPadMinMs), xDomainInitialWithoutPadding[1] + max(xInitPadPerSide*span(xDomainInitialWithoutPadding), xInitPadMinMs) ]"
},
{
"name": "xInitPadPerSide",
"description": "Padding (ms) applied per side of the initial x-domain. Computed from domain width and minimum padding constraints.",
"value": 0.05
},
{
"name": "xInitPadMinMs",
"description": "Minimum padding applied to x-domain ends (ms). Constant 86400000 (1 day).",
"value": 86400000
},
{
"name": "xDomain",
"description": "The current x-domain (min/max time) used by time scales. Computed from zoom, anchor, bounds, and constraints.",
"update": "[scale('xZoomScale0', xDomainZoomPercentage), scale('xZoomScale1', xDomainZoomPercentage)]"
},
{
"name": "xDomainAnchorInitial",
"description": "Initial anchor time used to align xDomain with scroll-map/anchor percentage. Computed from xDomainInitial and xDomainAnchorPercentage.",
"init": "xDomainInitial[0]+(span(xDomainInitial)/2)"
},
{
"name": "xDomainAnchor",
"description": "Animated/current anchor used for panning behavior and anchor animation. Computed from anchorAnimation/anchorTarget/xDomainAnchorInitial.",
"init": "xDomainAnchorInitial",
"on": [
{
"events": {"signal": "xDomainAnchorPercentage"},
"update": "scale('xAnchorScale', xDomainAnchorPercentage)"
}
]
},
{
"name": "maxXDomain",
"description": "Maximum allowed xDomain upper bound based on anchor + initial constraints. Computed from xDomainAnchor and xDomainInitial.",
"update": "[xDomainInitial[0]-span(xDomainInitial)/2, xDomainInitial[1]+span(xDomainInitial)/2]"
},
{
"name": "minXDomain",
"description": "Minimum allowed xDomain lower bound based on anchor + initial constraints. Computed from xDomainAnchor and xDomainInitial.",
"update": "[xDomainAnchor-msPerDay/12, xDomainAnchor+msPerDay/12]"
},
{
"name": "smallestAllowableXWidth",
"description": "Minimum permitted x-domain width (ms) to prevent over-zooming. Computed from msPerDay, xDomainInitial, and xDomainZoomPercentage.",
"value": 20
},
{
"name": "granularitySliderMouseDown",
"description": "Whether the date granularity slider is actively being dragged. Set on slider pointerdown and cleared on pointerup.",
"value": false,
"on": [
{
"events": "@granularity-slider-interactive-rect:pointerdown",
"update": "true"
},
{
"events": "@granularity-slider-interactive-rect:pointerup",
"update": "false"
}
]
},
{
"name": "showDetailsClick",
"description": "Tracks pointer state for the Show Details control for click feedback/gesture gating. Updated on show-details interactive rect pointerdown/pointerup.",
"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",
"description": "Computed layout position (x/y offsets) of the Gantt region based on left columns and overall view sizing.",
"init": "{x: data('column-data')[0].totalWidth, y:0, dy: 19}",
"on": [
{
"events": {"signal": "showDetailsClick.tEased"},
"update": "{x: ganttPosition.x + (data('column-data')[0].totalWidth - ganttPosition.x) * showDetailsClick.tEased, y: ganttPosition.y, dy: ganttPosition.dy}"
}
]
},
{
"name": "ganttDimensions",
"description": "Computed width/height of the Gantt region (excluding left columns and control regions). Derived from layout signals.",
"update": "{width: width-ganttPosition.x-2 - (actualHeight > adjustedHeight ? configVerticalScrollbar.track.width : 0), height: adjustedHeight}"
},
{
"name": "xCurrentTopLevelUnit",
"description": "Current secondary time unit (e.g., day/week) chosen based on xDomain and top-level unit for secondary header ticks.",
"update": "data('ganttTimeSeriesConfigurations')[0].tickCount"
},
{
"name": "xCurrentSecondaryUnit",
"description": "Determines the time unit displayed",
"update": "data('ganttTimeSeriesConfigurations')[0].secondaryTickCount"
},
{
"name": "xCurrentSecondaryBand",
"description": "Computed banding/tick spacing for secondary unit header rendering based on current units and domain.",
"update": "scale('xScaleGanttTimeSeries', xDomain[0]+(msPerDay*365/([1,12,365,8760][indexof(['year', 'month', 'day', 'hour'], xCurrentSecondaryUnit)])))"
},
{
"name": "wheel",
"description": "Captures the most recent wheel event payload used to drive vertical scrolling and/or zoom behavior depending on modifier keys/mode.",
"value": 1,
"on": [
{
"events": "wheel!",
"force": true,
"update": "pow(1.001, -event.deltaY * pow(16, event.deltaMode))"
}
]
},
{
"name": "timer",
"description": "Time driver signal used to advance animations and velocity decays; updates on a timer event.",
"init": "now()",
"on": [{"events": {"type": "timer", "throttle": 50}, "update": "now()"}]
},
{
"name": "zoomStart",
"description": "Starting zoom value used when animating zoom changes; updated when xDomainZoomPercentage changes.",
"value": 0.5,
"on": [
{
"events": "@granularity-slider-interactive-rect:click",
"update": "xDomainZoomPercentage"
},
{
"events": "@granularity-reset-interactive-rect:click",
"update": "xDomainZoomPercentage"
}
]
},
{
"name": "zoomTarget",
"description": "Target zoom value used when animating zoom changes; updated when xDomainZoomPercentage changes.",
"value": 0.5,
"on": [
{
"events": "@granularity-slider-interactive-rect:click",
"update": "clamp(x('granularity-slider-interactive-rect')/configDateGranularitySlider.track.width,0,1)"
},
{"events": "@granularity-reset-interactive-rect:click", "update": "0.5"}
]
},
{
"name": "granularityAnimation",
"description": "Interpolates zoom between zoomStart and zoomTarget while dragging/animating the granularity slider; computed from timer and drag state.",
"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",
"description": "Starting anchor percentage for anchor animation; updated when xDomainAnchorPercentage changes.",
"value": 0.5,
"on": [
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:click",
"update": "xDomainAnchorPercentage"
},
{
"events": "@granularity-reset-interactive-rect:click",
"update": "xDomainAnchorPercentage"
}
]
},
{
"name": "anchorTarget",
"description": "Target anchor percentage for anchor animation; updated when xDomainAnchorPercentage changes.",
"value": 0.5,
"on": [
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:click",
"update": "clamp(((x(group())-ganttPosition.x)/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",
"description": "Interpolates anchor percentage between anchorStart and anchorTarget over time; computed from timer and drag state.",
"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": "Current toggled value for Show Details (controls whether detail columns/row details render). Updated on show-details click.",
"init": "configShowDetails.initialValue",
"on": [
{
"events": "@show-details-interactive-rect:click{750}",
"update": "!showDetails"
}
]
},
{
"name": "panAndZoomMode",
"description": "Whether the visualization is in pan-and-zoom mode (changes wheel behavior and drag semantics). Toggled by pan-and-zoom control click or Space key.",
"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": "showBarLabels",
"description": "Whether task bar labels render on the Gantt bars. Toggled by the show-bar-labels control.",
"init": "hasLabels",
"on": [
{
"events": "@show-bar-labels-interactive-rect:click",
"update": "!showBarLabels"
}
]
},
{
"name": "showDependencies",
"description": "Whether dependency links render. Toggled by the show-dependencies control.",
"init": "hasDependencies",
"on": [
{
"events": "@show-dependencies-interactive-rect:click",
"update": "!showDependencies"
}
]
},
{
"name": "ctrlKey",
"description": "Tracks whether the Control key is currently pressed (used for gesture modifiers). Updated on window keydown/keyup for Control.",
"value": false,
"on": [
{"events": "window:keydown[event.ctrlKey]", "update": "true"},
{"events": "window:keyup", "update": "false"}
]
},
{
"name": "shiftKey",
"description": "Tracks whether the Shift key is currently pressed (used for gesture modifiers). Updated on window keydown/keyup for Shift.",
"value": false,
"on": [
{"events": "window:keydown[event.shiftKey]", "update": "true"},
{"events": "window:keyup", "update": "false"}
]
},
{
"name": "currentMaxIndentWidth",
"description": "Computed maximum indentation width currently required (based on visible hierarchy depth). Used to size/position left column content.",
"update": "(extent(pluck(data('hierarchy-initial'), 'level'))[1]-1)*configRow.levelIndentWidth"
},
{
"name": "scrollY",
"description": "Computed pixel scroll offset in Y derived from verticalScrollPercentage and scrollable height; used to translate row content.",
"update": "actualHeight > adjustedHeight ? clamp(-verticalScrollPercentage*actualHeight, -(actualHeight-adjustedHeight), 2*ganttPosition.dy) : 0"
},
{
"name": "msPerDay",
"description": "Constant milliseconds per day used for date math and tick calculations. 86400000.",
"value": 86400000
},
{
"name": "sortHistory",
"description": "Tracks sort click history for column-header sorting (e.g., field, repeated click toggles, third click clears). Updated on column-header pointerdown.",
"init": "[]",
"on": [
{
"events": "@column-labels-interactive-rect:click",
"update": "isAnimating ? sortHistory : (datum.datum.field === sortHistory[0] && datum.datum.field === sortHistory[1]) ? [] : length(sortHistory) === 0 ? [datum.datum.field] : split((datum.datum.field+','+join(sortHistory)),',',2)"
}
]
},
{
"name": "sortField",
"description": "The currently active sort field (column field name). Derived from sortHistory via a signal event; null when not sorting.",
"init": "null",
"on": [
{
"events": {"signal": "sortHistory"},
"update": "isAnimating ? sortField : length(sortHistory) === 0 ? null : sortHistory[0]"
}
]
},
{
"name": "sortOrder",
"description": "The current sort order ('ascending'/'descending'). Derived from sortHistory via a signal event; toggles when same field clicked repeatedly.",
"value": "ascending",
"on": [
{
"events": {"signal": "sortHistory"},
"update": "isAnimating ? sortOrder : !isValid(sortHistory[0]) || !isValid(sortHistory[1]) || sortHistory[0] !== sortHistory[1] ? 'ascending' : sortOrder === 'ascending' ? 'descending' : 'ascending'"
}
]
},
{
"name": "mouseoverColumnHeader",
"description": "Stores the column header field currently hovered (for hover styling/tooltips). Updated on column-header mouseover/mouseout.",
"init": "[]",
"on": [
{
"events": "@column-labels-interactive-rect:mouseover",
"update": "datum.datum.field"
},
{"events": "@column-labels-interactive-rect:mouseout", "update": "null"}
]
},
{
"name": "actualHeight",
"description": "Computed total content height of visible rows (used to determine scroll range and scrollbar sizing). Derived from hierarchy-visible-target and row config.",
"update": "data('height-animation')[0]['animatedHeight']"
},
{
"name": "adjustedHeight",
"description": "Computed available height for the main plot after subtracting UI/control regions; used for viewport/buffering calculations.",
"update": "min((configIsPowerBIVisual ? ((containerSize()[1] || windowSize()[1])-110) : configDesiredGanttHeight), actualHeight)"
},
{
"name": "height",
"description": "Final view height after applying desiredHeight and host constraints (Power BI vs non-PBI).",
"update": "min(data('height-animation')[0].animatedHeight, adjustedHeight)"
},
{
"name": "width",
"description": "Final view width after applying desiredWidth and host constraints (Power BI vs non-PBI).",
"update": "configIsPowerBIVisual ? containerSize()[0] || windowSize()[0] : configDesiredWidth"
},
{
"name": "initialTimestamp",
"description": "Captured timestamp at initialization (used as a stable reference for elapsed time/animation gating).",
"init": "now()"
},
{
"name": "hasLabels",
"description": "Whether any currently-visible rows have labels enabled/available; used to toggle label-related layout/marks. Computed from showBarLabels + visible data.",
"update": "isValid(extent(pluck(data('dataset'), 'label'))[1])"
},
{
"name": "hasDependencies",
"description": "Whether dependencies are enabled/available for the current view; used to toggle dependency-related layout/marks. Computed from showDependencies + visible data.",
"update": "isValid(extent(pluck(data('dataset'), 'dependencyId'))[1])"
},
{
"name": "animateCount",
"description": "Counter incremented when a row animation completes/advances (often keyed off rowAnimation reaching 1). Used to trigger dependent refresh logic.",
"init": "0",
"on": [
{
"events": {"signal": "lastClickedNode"},
"update": "isAnimating || !isValid(lastClickedNode) || !lastClickedNode.datum.hasChildren ? animateCount : animateCount + 1"
},
{
"events": {"signal": "expandCollapseButtonDatum"},
"update": "isAnimating || !isValid(expandCollapseButtonDatum) ? animateCount : animateCount + 1"
}
]
},
{
"name": "sortAnimateCount",
"init": "0",
"on": [
{
"events": [{"signal": "sortHistory"}],
"update": "!isAnimating ? sortAnimateCount + 1 : sortAnimateCount"
}
]
},
{"name": "animationPulse", "update": "animateCount + sortAnimateCount"},
{
"name": "isAnimating",
"description": "True while an expand/collapse/sort animation is running; computed from timer and rowAnimation state to gate interactions.",
"init": "false",
"on": [
{
"events": {"type": "timer"},
"update": "!isValid(data('hierarchy-animation-bounds')) ? false : !(timer > data('hierarchy-animation-bounds')[0].end+500)"
}
]
},
{
"name": "animStartTick",
"description": "Timestamp (tick) captured at the start of an interaction that should animate (row click, button press, sort). Set on pointerdown events.",
"init": "0",
"on": [
{
"events": "@gantt-row-clickable-rect:pointerup, @column-row-background-rect:pointerup",
"update": "!isAnimating && datum.hasChildren && datum.level >= configInitialDepth ? timer : animStartTick"
},
{
"events": "@expand-all-collapse-all-button-background:pointerdown{0,0}",
"update": "rowAnimationTEased === 1 ? timer : animStartTick"
},
{
"events": [{"signal": "sortAnimateCount"}],
"update": "!isAnimating ? timer : animStartTick"
}
]
},
{
"name": "rowAnimation",
"description": "Normalized animation progress [0..1] for row transitions; computed from timer, animStartTick, configAnimationDuration, and interactionTypeHistory.",
"update": "{active: (timer < (animStartTick + (interactionTypeHistory[0] === 'sort' ? configAnimationDuration.sort : configAnimationDuration.nodeExpandCollapse))), t: clamp((timer - animStartTick) / (interactionTypeHistory[0] === 'sort' ? configAnimationDuration.sort : configAnimationDuration.nodeExpandCollapse), 0, 1)}"
},
{
"name": "rowAnimationTEased",
"description": "Eased version of rowAnimation.t used for smoother interpolation (lerp) of y/opacity values.",
"update": "rowAnimation.t < 0.5 ? 4*pow(rowAnimation.t,3) : 1 - pow(-2*rowAnimation.t + 2, 3)/2"
},
{
"name": "settingsIndicatorClicked",
"description": "Whether the settings indicator is currently 'clicked/open' (controls visibility of settings UI). Updated on settings indicator click/mouseout/mousemove.",
"init": "false",
"on": [
{
"events": "@settings-indicator-rect:pointerup",
"update": "!settingsIndicatorClicked"
},
{"events": "@settings-backdrop:pointerup", "update": "false"}
]
},
{
"name": "settingsIndicatorMouseover",
"description": "Whether the settings indicator is hovered. Updated on settings indicator mousemove/mouseout.",
"init": "false",
"on": [
{"events": "@settings-indicator-rect:mouseover", "update": "true"},
{"events": "@settings-indicator-rect:mouseout", "update": "false"}
]
},
{
"name": "settingsIndicatorClickedCount",
"description": "Counter incremented on settings indicator click; used to trigger menu open/close animations.",
"init": "0",
"on": [
{
"events": {"signal": "settingsIndicatorClicked"},
"update": "settingsIndicatorClickedCount + (settingsIndicatorClicked ? 1 : 0)"
}
]
},
{
"name": "settingsMenuAnimStart",
"description": "Timestamp/tick captured when the settings menu animation begins; set when settingsIndicatorClickedCount changes.",
"init": "null",
"on": [
{
"events": "@settings-indicator-rect:pointerup{0,0}",
"update": "now()"
},
{"events": "@settings-backdrop:pointerup{0,0}", "update": "now()"}
]
},
{
"name": "initStamp",
"description": "The very initial timer tick. Used to trigger initialization for the 'hierarchy-last-visible-target' dataset and the 'hierarchy-pre-animation' dataset.",
"value": null,
"on": [
{
"events": {"type": "timer"},
"update": "initStamp==null ? timer : initStamp"
}
]
}
],
"marks": [
{
"name": "group-everything",
"type": "group",
"interactive": false,
"encode": {
"update": {
"width": {"signal": "width"},
"height": {"signal": "height"},
"fill": {"signal": "background"}
}
},
"marks": [
{
"name": "group-header",
"type": "group",
"interactive": false,
"encode": {"enter": {"y": {"value": -65}}},
"marks": [
{
"name": "group-expand-all-collapse-all-buttons",
"description": "the group of marks that make up the expand/collapse buttons",
"type": "group",
"interactive": false,
"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": "configExpandCollapseButtons.label.text || ''"
},
"baseline": {"value": "top"},
"font": {
"signal": "configExpandCollapseButtons.label.font"
},
"fontSize": {
"signal": "configExpandCollapseButtons.label.fontSize"
},
"fontStyle": {
"signal": "configExpandCollapseButtons.label.fontStyle"
},
"align": {"value": "left"},
"fill": {
"signal": "configExpandCollapseButtons.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": "configExpandCollapseButtons.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"}
}
}
},
{
"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-configExpandCollapseButtons.padding"
},
"x2": {
"signal": "datum.bounds.x2+configExpandCollapseButtons.padding"
},
"y": {
"signal": "datum.bounds.y1-configExpandCollapseButtons.padding"
},
"y2": {
"signal": "datum.bounds.y2+configExpandCollapseButtons.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": "configExpandCollapseButtons.outerStroke"
},
"fill": {"signal": "background || '#fff'"},
"opacity": {"value": 1},
"cursor": {"value": "pointer"},
"tooltip": {"signal": "datum.datum.tooltip"}
},
"update": {
"stroke": {
"signal": "configExpandCollapseButtons.outerStroke"
},
"strokeOpacity": {"value": 0.65}
},
"hover": {"strokeOpacity": {"value": 1}}
}
},
{
"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": false,
"encode": {
"enter": {
"x": {
"signal": "datum.bounds.x1-configExpandCollapseButtons.padding"
},
"x2": {
"signal": "datum.bounds.x2+configExpandCollapseButtons.padding"
},
"y": {
"signal": "datum.bounds.y1-configExpandCollapseButtons.padding"
},
"y2": {
"signal": "datum.bounds.y2+configExpandCollapseButtons.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},
"cursor": {"value": "pointer"},
"tooltip": {"signal": "datum.datum.tooltip"}
},
"update": {
"fill": {
"signal": "isValid(expandCollapseButtonDatum) && expandCollapseButtonDatum.rowNumber === datum.datum.rowNumber ? configExpandCollapseButtons.hoverFill : configExpandCollapseButtons.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": false,
"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"}
}
}
},
{
"name": "expand-all-collapse-all-clickStopper",
"description": "prevents repetitive expand all / collapse all clicks",
"type": "rect",
"from": {"data": "expand-all-collapse-all-button-background"},
"interactive": true,
"encode": {
"update": {
"tooltip": {"signal": "datum.datum.datum.tooltip"},
"x": {"signal": "datum.bounds.x1"},
"width": {
"signal": "isAnimating ? datum.bounds.x2-datum.bounds.x1 : !isValid(expansionHistory[0]) || datum.datum.datum.name !== expansionHistory[0] ? 0 : datum.bounds.x2-datum.bounds.x1"
},
"y": {"signal": "datum.bounds.y1"},
"y2": {"signal": "datum.bounds.y2"},
"cornerRadiusTopLeft": {"field": "cornerRadiusTopLeft"},
"cornerRadiusBottomLeft": {
"field": "cornerRadiusBottomLeft"
},
"cornerRadiusTopRight": {"field": "cornerRadiusTopRight"},
"cornerRadiusBottomRight": {
"field": "cornerRadiusBottomRight"
},
"fill": {
"signal": "datum.datum.datum.name !== expansionHistory[0] ? 'transparent' : 'steelblue'"
},
"fillOpacity": {"value": 0.25}
}
}
}
]
},
{
"name": "settings-icon-group",
"type": "group",
"signals": [
{
"name": "settingsIndicatorMouseover",
"init": "false",
"on": [
{
"events": "@settings-indicator-rect:mouseover",
"update": "true"
},
{
"events": "@settings-indicator-rect:mouseout",
"update": "false"
}
]
}
],
"encode": {
"update": {
"y": {"signal": "-2.5"},
"x": {"signal": "width", "offset": -22.5},
"width": {"signal": "15"},
"height": {"signal": "15"}
}
},
"marks": [
{
"name": "settings-indicator",
"type": "symbol",
"interactive": false,
"encode": {
"update": {
"shape": {
"value": "M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"
},
"size": {"value": 0.0025},
"fill": {
"signal": "settingsIndicatorMouseover ? '#555' : '#bbb' "
}
}
}
},
{
"name": "settings-indicator-rect",
"type": "rect",
"from": {"data": "settings-indicator"},
"encode": {
"update": {
"tooltip": {"value": "Settings"},
"x": {"signal": "datum.bounds.x1"},
"x2": {"signal": "datum.bounds.x2"},
"y": {"signal": "datum.bounds.y1"},
"y2": {"signal": "datum.bounds.y2"},
"fill": {"value": "transparent"},
"cursor": {"value": "pointer"}
}
}
}
]
}
]
},
{
"name": "column-group",
"type": "group",
"interactive": false,
"encode": {
"update": {
"x": {"signal": "0"},
"y": {"signal": "0"},
"x2": {"signal": "ganttPosition.x"},
"height": {"signal": "(ganttDimensions.height)"}
}
},
"marks": [
{
"name": "column-header-group",
"type": "group",
"interactive": false,
"encode": {
"update": {
"x": {"signal": "configRow.levelIndentWidth"},
"y": {"signal": "-ganttPosition.dy"},
"height": {"signal": "ganttPosition.dy"},
"width": {"signal": "ganttPosition.x"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "column-header-label-group",
"type": "group",
"interactive": false,
"encode": {
"update": {
"x": {"signal": "0"},
"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": "column-data"},
"interactive": false,
"encode": {
"update": {
"text": {
"signal": "datum.label + (sortField === datum.field ? sortOrder === 'ascending' ? '▴' : '▾' : '')"
},
"x": {
"signal": "datum.align === 'left' ? datum.x : datum.align === 'right' ? datum.x2 : datum.x + (datum.x2-datum.x)/2"
},
"dx": {
"signal": "datum.align === 'left' ? 1 : datum.align === 'right' ? -3 : 0"
},
"y": {"signal": "ganttPosition.dy/2"},
"baseline": {"value": "middle"},
"limit": {"signal": "datum.x2-datum.x1"},
"align": {"field": "align"},
"fontSize": {"value": 14},
"font": {"value": "Segoe UI"},
"fill": {
"signal": "isValid(mouseoverColumnHeader) && mouseoverColumnHeader === datum.field ? '#000' : '#777'"
},
"fontWeight": {"value": "600"}
}
}
},
{
"name": "column-labels-interactive-rect",
"description": "interactive rects for column header text labels",
"type": "rect",
"from": {"data": "column-labels"},
"interactive": true,
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1"},
"y": {"signal": "datum.bounds.y1"},
"width": {"signal": "datum.datum.x2-datum.datum.x"},
"height": {
"signal": "datum.bounds.y2-datum.bounds.y1"
},
"fill": {"value": "transparent"},
"cursor": {
"signal": "isAnimating ? 'default' : 'pointer'"
}
}
}
}
]
}
]
},
{
"name": "static-column-row-group",
"type": "group",
"clip": true,
"interactive": false,
"encode": {
"update": {
"x": {"signal": "0"},
"y": {"signal": "0"},
"x2": {"signal": "ganttPosition.x"},
"height": {"signal": "ganttDimensions.height+6"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "scrollable-column-row-group",
"type": "group",
"interactive": false,
"encode": {
"update": {
"x": {"signal": "0"},
"y": {"signal": "scrollY"},
"x2": {"signal": "ganttPosition.x"},
"height": {"signal": "ganttDimensions.height"}
}
},
"data": [
{
"name": "column-row-data",
"source": "hierarchy-animation",
"transform": [
{
"type": "formula",
"expr": "!isValid(datum.sourceValues) || !datum.hasChildren ? 0 : datum.sourceValues.isExpanded === 1 ? 90 : 0",
"as": "sourceIndicatorAngle"
},
{
"type": "formula",
"expr": "!isValid(datum.targetValues) || !datum.hasChildren ? 0 : datum.targetValues.isExpanded === 1 ? 90 : 0",
"as": "targetIndicatorAngle"
},
{
"type": "formula",
"expr": "lerp([datum.sourceIndicatorAngle, datum.targetIndicatorAngle], rowAnimationTEased)",
"as": "indicatorAngle"
}
]
}
],
"marks": [
{
"name": "column-row-background-rect",
"type": "rect",
"from": {"data": "column-row-data"},
"interactive": {"signal": "rowAnimationTEased === 1"},
"encode": {
"update": {
"tooltip": {
"signal": "data('gantt-item-tooltip-data')[0]"
},
"x": {"value": 0},
"x2": {"signal": "ganttDimensions.width"},
"y": {"field": "y1"},
"y2": {"field": "y2"},
"fill": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.id ? 'rgba(237, 246, 252, 0.5)' : background"
},
"fillOpacity": {"field": "opacity"},
"cursor": {
"signal": "!isAnimating && datum.hasChildren && datum.level >= configInitialDepth ? 'pointer' : 'default'"
}
}
}
},
{
"name": "column-row-divider-rect",
"type": "rect",
"from": {"data": "column-row-data"},
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "configRow.levelIndentWidth*((datum.level)+0.15)"
},
"x2": {"signal": "ganttDimensions.width"},
"y": {"field": "y2", "offset": -0.2},
"y2": {"field": "y2", "offset": 0.2},
"fill": {"value": "#D8D8DA"},
"fillOpacity": {"field": "opacity"}
}
}
},
{
"name": "expand-collapse-indicator",
"description": "the arrow indicator for parent nodes that indicate whether the current state of the node is expanded (arrow pointing down) or collapsed (arrow pointing to the right).",
"type": "text",
"from": {"data": "column-row-data"},
"interactive": false,
"encode": {
"update": {
"text": {"signal": "!datum.hasChildren ? '' : '➤'"},
"angle": {"field": "indicatorAngle"},
"x": {
"signal": "datum.hasChildren ? configRow.levelIndentWidth*((datum.level)-0.4) : null"
},
"y": {"signal": "datum.y1+configRow.rowHeight/2"},
"fontSize": {"signal": "12"},
"fill": {"field": "color"},
"fillOpacity": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.id ? 1 : 0.5"
},
"align": {"value": "center"},
"baseline": {"value": "middle"},
"opacity": {"field": "fullOpacity"},
"fontWeight": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.id ? 800 : 400"
}
}
}
},
{
"name": "row-column-group",
"type": "group",
"from": {"data": "column-data"},
"interactive": false,
"data": [
{
"name": "row-text-data",
"source": "hierarchy-animation",
"transform": [
{
"type": "filter",
"expr": "parent.type === 'text' || parent.type === 'date'"
},
{
"type": "lookup",
"from": "dataset-formatted",
"key": "id",
"fields": ["id"],
"as": ["originalValues"]
},
{
"type": "formula",
"expr": "isValid(datum.originalValues) && isValid(parent.field) ? datum.originalValues[parent.field] : null",
"as": "value"
},
{"type": "filter", "expr": "isValid(datum.value)"},
{
"type": "formula",
"expr": "isValid(parent.type) && parent.type === 'date' && isValid(parent.format) ? timeFormat(datum.value, parent.format) : datum.value",
"as": "value"
},
{
"type": "formula",
"expr": "isValid(parent.type) && parent.type === 'text' && isValid(parent.format) ? format(datum.value, parent.format) : datum.value",
"as": "value"
},
{
"type": "formula",
"expr": "parent.index",
"as": "columnIndex"
},
{
"type": "formula",
"expr": "parent.align",
"as": "columnAlign"
},
{
"type": "formula",
"expr": "parent.x+(datum.columnAlign === 'left' ? 1 : 0)",
"as": "columnX"
},
{
"type": "formula",
"expr": "parent.x2+(datum.columnAlign === 'right' ? -3 : 0 )",
"as": "columnX2"
},
{
"type": "formula",
"expr": "parent.boldValue",
"as": "columnBoldValue"
},
{
"type": "formula",
"expr": "parent.allowableWidth",
"as": "allowableWidth"
}
]
},
{
"name": "row-percentage-data",
"source": "hierarchy-animation",
"transform": [
{
"type": "filter",
"expr": "parent.type === 'percentage'"
},
{
"type": "lookup",
"from": "dataset-formatted",
"key": "id",
"fields": ["id"],
"as": ["originalValues"]
},
{
"type": "formula",
"expr": "isValid(datum.originalValues) && isValid(parent.field) ? datum.originalValues[parent.field] : null",
"as": "value"
},
{"type": "filter", "expr": "isValid(datum.value)"},
{
"type": "formula",
"expr": "isValid(parent.type) && parent.type === 'percentage' && isValid(parent.format) ? format(datum.value, parent.format) : datum.value",
"as": "value"
},
{
"type": "formula",
"expr": "parent.index",
"as": "columnIndex"
},
{
"type": "formula",
"expr": "parent.align",
"as": "columnAlign"
},
{
"type": "formula",
"expr": "parent.x+(datum.columnAlign === 'left' ? 1 : 0)",
"as": "columnX"
},
{
"type": "formula",
"expr": "parent.x2+(datum.columnAlign === 'right' ? -3 : 0 )",
"as": "columnX2"
},
{
"type": "formula",
"expr": "parent.boldValue",
"as": "columnBoldValue"
}
]
}
],
"encode": {
"update": {
"x": {
"field": "x",
"offset": {"signal": "configRow.levelIndentWidth"}
},
"x2": {
"field": "x2",
"offset": {"signal": "configRow.levelIndentWidth"}
},
"y": {"signal": "0"},
"height": {"signal": "ganttDimensions.height"}
}
},
"marks": [
{
"name": "column-text-cell-group",
"type": "group",
"interactive": false,
"from": {"data": "row-text-data"},
"encode": {
"update": {
"x": {
"signal": "parent.index === 1 ? configRow.levelIndentWidth*((datum.level)-0.8) : 0"
},
"width": {
"signal": "datum.columnX2-datum.columnX-(parent.index === 1 ? configRow.levelIndentWidth*((datum.level)-0.8) : 0)"
},
"y": {"field": "y1"},
"y2": {"field": "y2"},
"fill": {"value": "transparent"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "text-value",
"type": "text",
"interactive": false,
"encode": {
"update": {
"text": {"signal": "parent.value"},
"x": {
"signal": "parent.columnAlign === 'right' ? parent.columnX2-parent.columnX : parent.columnAlign === 'center' ? (parent.columnX2-parent.columnX)/2 : 0"
},
"y": {"signal": "configRow.rowHeight/2"},
"baseline": {"value": "middle"},
"opacity": {"signal": "parent.fullOpacity"},
"align": {"signal": "parent.columnAlign"},
"fontSize": {
"signal": "configColumn.values.fontSize"
},
"fill": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.id ? '#000' : '#666'"
},
"fontWeight": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === parent.id || parent.columnBoldValue ? 700 : configColumn.fontWeight"
},
"limit": {
"signal": "parent.columnX2-parent.columnX-(parent.columnIndex === 1 ? configRow.levelIndentWidth*((parent.level)-0.8) : 0)"
}
}
}
}
]
},
{
"name": "column-percentage-cell-group",
"type": "group",
"interactive": false,
"from": {"data": "row-percentage-data"},
"encode": {
"update": {
"x": {
"signal": "parent.index === 1 ? configRow.levelIndentWidth*((datum.level)-0.8) : 0"
},
"width": {
"signal": "datum.columnX2-datum.columnX-(parent.index === 1 ? configRow.levelIndentWidth*((datum.level)-0.8) : 0)"
},
"y": {"field": "y1"},
"y2": {"field": "y2"},
"fill": {"value": "transparent"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "percentage-container",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "(parent.columnX2-parent.columnX)*0.15"
},
"width": {
"signal": "(parent.columnX2-parent.columnX)*0.7"
},
"y": {"signal": "configRow.rowHeight*0.175"},
"y2": {"signal": "configRow.rowHeight*0.825"},
"cornerRadius": {
"signal": "(configRow.rowHeight)*0.1"
},
"opacity": {"signal": "parent.fullOpacity"},
"stroke": {"signal": "parent.color"},
"strokeOpacity": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === parent.id ? 0.85 : 0.65"
},
"strokeWidth": {"value": 1.5}
}
}
},
{
"name": "percentage-bar",
"type": "rect",
"from": {"data": "percentage-container"},
"interactive": false,
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1"},
"width": {
"signal": "(datum.bounds.x2-datum.bounds.x1)*parent.decimalPercentComplete"
},
"y": {
"signal": "datum.bounds.y1+datum.strokeWidth"
},
"y2": {
"signal": "datum.bounds.y2-datum.strokeWidth"
},
"fill": {"field": "stroke"},
"cornerRadius": {"field": "cornerRadius"},
"fillOpacity": {
"signal": "datum.strokeOpacity-0.25"
},
"opacity": {"field": "opacity"}
}
}
},
{
"name": "percentage-text",
"type": "text",
"from": {"data": "percentage-container"},
"interactive": false,
"encode": {
"update": {
"text": {"signal": "parent.value"},
"x": {
"signal": "datum.bounds.x1+(datum.bounds.x2-datum.bounds.x1)*0.1"
},
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"fill": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.id ? '#000' : '#666'"
},
"fontWeight": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === parent.id || parent.columnBoldValue ? 700 : configColumn.fontWeight"
},
"align": {"value": "left"},
"baseline": {"value": "middle"},
"fillOpacity": {
"signal": "parent.fullOpacity"
},
"opacity": {"field": "opacity"}
}
}
}
]
}
]
},
{
"name": "column-click-stopper-rect",
"type": "rect",
"zindex": 9999,
"interactive": {"signal": "isAnimating"},
"encode": {
"update": {
"x": {"value": 0},
"y": {"value": 0},
"width": {
"signal": "isAnimating ? ganttPosition.x : 0"
},
"height": {
"signal": "ganttPosition.dy + ganttDimensions.height-scrollY"
},
"cursor": {"value": "default"},
"fill": {"value": "transparent"}
}
}
}
]
}
]
}
]
},
{
"name": "gantt-group",
"type": "group",
"interactive": false,
"encode": {
"enter": {"y": {"signal": "-ganttPosition.dy"}},
"update": {
"x": {"signal": "ganttPosition.x"},
"width": {"signal": "ganttDimensions.width"},
"height": {
"signal": "ganttDimensions.height+ganttPosition.dy+horizontalScrollMapHeight*2 + configDateGranularitySlider.track.height*3"
},
"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",
"encode": {
"update": {
"y": {"signal": "-ganttPosition.dy"},
"width": {"signal": "ganttDimensions.width"},
"height": {
"signal": "ganttPosition.dy*2+ganttDimensions.height"
},
"clip": {"value": true}
}
},
"data": [
{
"name": "weekend-dates",
"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": "weekend-dates"},
"encode": {
"update": {
"x": {"scale": "xScaleGanttTimeSeries", "field": "date"},
"x2": {
"scale": "xScaleGanttTimeSeries",
"signal": "+datum.date+msPerDay-1"
},
"y": {"signal": "2*ganttPosition.dy"},
"height": {"signal": "ganttDimensions.height"},
"fill": {"value": "#dce3e8"},
"fillOpacity": {"value": 0.1}
}
}
},
{
"name": "secondary-axis-group",
"type": "group",
"encode": {
"update": {
"y": {"signal": "ganttPosition.dy"},
"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"
},
"fillOpacity": {
"signal": "max(1-(length(data('secondary-axis-level-dummy-axis-labels'))/60), 0.25)"
}
}
}
}
]
},
{
"name": "top-level-axis-group",
"type": "group",
"encode": {
"update": {
"x": {"signal": "0"},
"y": {"signal": "0"},
"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",
"clip": true,
"encode": {
"update": {
"y": {"signal": "ganttPosition.dy"},
"width": {"signal": "ganttDimensions.width"},
"height": {"signal": "ganttDimensions.height"},
"clip": {"value": true}
}
},
"signals": [{"name": "today", "update": "now()"}],
"marks": [
{
"name": "rect-gantt-background",
"type": "rect",
"encode": {
"update": {
"width": {"signal": "ganttDimensions.width"},
"height": {"signal": "adjustedHeight"},
"fill": {"value": "transparent"},
"cursor": {
"signal": "!panAndZoomMode ? 'default' : panning ? 'grabbing' : 'grab'"
}
}
}
},
{
"name": "gantt-canvas-group",
"type": "group",
"encode": {"update": {"y": {"signal": "scrollY"}}},
"data": [
{
"name": "gantt-rect-data",
"source": "hierarchy-animation",
"transform": [
{
"type": "filter",
"expr": "toNumber(datum.endDate) >= xDomain[0] && toNumber(datum.startDate) <= xDomain[1]"
},
{
"type": "filter",
"expr": "datum.startDate !== datum.endDate"
}
]
},
{
"name": "gantt-milestone-data",
"source": "hierarchy-animation",
"transform": [
{
"type": "filter",
"expr": "toNumber(datum.endDate) >= xDomain[0] && toNumber(datum.startDate) <= xDomain[1]"
},
{
"type": "filter",
"expr": "datum.startDate === datum.endDate"
}
]
},
{
"name": "gantt-label-data",
"source": "hierarchy-animation",
"transform": [
{"type": "filter", "expr": "showBarLabels"},
{
"type": "filter",
"expr": "toNumber(datum.endDate) >= xDomain[0] && toNumber(datum.startDate) <= xDomain[1]"
},
{
"type": "formula",
"expr": "datum.index === 1 || datum.index === 2",
"as": "isATopRow"
},
{
"type": "formula",
"expr": "scale('xScaleGanttTimeSeries', today)",
"as": "xToday"
},
{
"type": "formula",
"expr": "abs(datum.xToday-datum.x1)",
"as": "startTodayProximity"
},
{
"type": "formula",
"expr": "abs(datum.xToday-datum.x2)",
"as": "endTodayProximity"
},
{
"type": "formula",
"expr": "ganttDimensions.width - datum.x2",
"as": "ganttWidthProximity"
},
{
"type": "formula",
"expr": "isValid(datum.label) ? length(datum.label)*configGantt.label.fontSize/1.9 : 0",
"as": "approxLabelWidth"
},
{
"type": "formula",
"expr": "datum.ganttWidthProximity < datum.approxLabelWidth || (indexof([1,2], datum.index) >= 0 && datum.endTodayProximity < datum.approxLabelWidth) ? 'start' : 'end'",
"as": "anchor"
},
{
"type": "formula",
"expr": "datum.anchor === 'start' ? 'right' : 'left'",
"as": "align"
},
{
"type": "formula",
"expr": "datum.anchor === 'start' ? datum.x1-configRow.rowHeight/2 : datum.x2+configRow.rowHeight/2",
"as": "x"
}
]
}
],
"marks": [
{
"name": "today-line-group",
"type": "group",
"marks": [
{
"name": "today-line-rect",
"description": "the vertical dashed line that indicates today's date in the gantt",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "scale('xScaleGanttTimeSeries', today)"
},
"x2": {
"signal": "scale('xScaleGanttTimeSeries', today)"
},
"y": {"signal": "0"},
"y2": {
"signal": "length(data('hierarchy-initial'))*configRow.rowHeight"
},
"fill": {"value": "transparent"},
"stroke": {
"signal": "configGantt.todayLine.stroke"
},
"strokeWidth": {
"signal": "configGantt.todayLine.strokeWidth"
},
"strokeDash": {
"signal": "configGantt.todayLine.strokeDash"
},
"strokeDashOffset": {
"signal": "configGantt.todayLine.strokeDashOffset"
}
}
}
},
{
"name": "today-line-text-background",
"type": "text",
"interactive": false,
"encode": {
"update": {
"text": {
"signal": "datum.labelOpacity === 0 ? '' : 'Today'"
},
"x": {
"signal": "scale('xScaleGanttTimeSeries', today)"
},
"dx": {"signal": "5"},
"y": {"signal": "-scrollY"},
"dy": {"signal": "-2.5"},
"angle": {"value": 90},
"baseline": {"value": "bottom"},
"font": {"signal": "configGantt.label.font"},
"fontSize": {
"signal": "configGantt.label.fontSize"
},
"fontWeight": {
"signal": "configGantt.label.fontWeight"
},
"fill": {"signal": "background"},
"stroke": {"signal": "background"},
"strokeWidth": {"value": 4},
"opacity": {
"signal": "!isValid(data('hierarchy-animation')[0]) || !isValid(data('hierarchy-animation')[0].x1) || !isValid(data('hierarchy-animation')[1]) || !isValid(data('hierarchy-animation')[1].x1) ? 0 : !inrange(scale('xScaleGanttTimeSeries', today), [data('hierarchy-animation')[0].x1-10, data('hierarchy-animation')[0].x2+10]) && !inrange(scale('xScaleGanttTimeSeries', today), [data('hierarchy-animation')[1].x1-10, data('hierarchy-animation')[1].x2+10]) ? 1 : 0"
}
}
}
},
{
"name": "today-line-text",
"description": "the text mark for the today line",
"type": "text",
"from": {"data": "today-line-text-background"},
"interactive": false,
"encode": {
"update": {
"text": {"field": "text"},
"x": {"field": "x"},
"dx": {"field": "dx"},
"y": {"field": "y"},
"dy": {"field": "dy"},
"angle": {"field": "angle"},
"baseline": {"field": "baseline"},
"font": {"field": "font"},
"fontSize": {"field": "fontSize"},
"fontWeight": {"field": "fontWeight"},
"fill": {"signal": "configGantt.label.fill"},
"opacity": {"field": "opacity"}
}
}
}
]
},
{
"name": "dependency-links-group",
"description": "group that makes up the dependency marks in the Gantt (i.e. arrowed lines)",
"type": "group",
"interactive": false,
"from": {
"facet": {
"name": "dependency-links-facet",
"data": "dependency-links",
"groupby": [
"sourceId",
"targetId",
"targetTargetValues"
]
}
},
"data": [
{
"name": "transformed-dependency-links-facet",
"source": "dependency-links-facet",
"transform": [
{"type": "filter", "expr": "showDependencies"},
{
"type": "formula",
"expr": "isValid(mouseoverNodeDatum) && (datum.sourceId === mouseoverNodeDatum.id || datum.targetId === mouseoverNodeDatum.id)",
"as": "hasMouseOver"
},
{
"type": "formula",
"expr": "datum.hasMouseOver && (datum.sourceId === mouseoverNodeDatum.id || datum.targetId === mouseoverNodeDatum.id)",
"as": "isMousedOver"
}
]
}
],
"marks": [
{
"name": "dependency-links-line",
"description": "the dependency line mark",
"type": "line",
"from": {
"data": "transformed-dependency-links-facet"
},
"encode": {
"update": {
"x": {
"signal": "!isValid(datum) || !isValid(datum.xy) ? 0 : datum.xy.x"
},
"y": {
"signal": "!isValid(datum) || !isValid(datum.xy) ? 0 : datum.xy.y"
},
"stroke": {
"signal": "datum.isMousedOver ? '#111' : '#666'"
},
"opacity": {"field": "opacity"},
"strokeWidth": {
"signal": "datum.isMousedOver ? 2.5 : 1.5"
},
"interpolate": {"value": "linear"},
"strokeJoin": {"value": "bevel"},
"strokeCap": {"value": "round"},
"defined": {"value": true}
}
}
},
{
"name": "dependency-links-arrow",
"description": "the dependency arrow mark",
"type": "text",
"from": {
"data": "transformed-dependency-links-facet"
},
"encode": {
"update": {
"x": {
"signal": "!isValid(datum) || !isValid(datum.xy) ? 0 : datum.xy.x",
"offset": {
"signal": "datum.targetTargetValues.startDate === datum.targetTargetValues.endDate ? -2.5 : 1"
}
},
"y": {
"signal": "!isValid(datum) || !isValid(datum.xy) ? 0 : datum.xy.y"
},
"dy": {"value": 0.999},
"text": {
"signal": "!isValid(datum) || !isValid(datum.sort) ? null : datum.sort === 6 ? '⮞' : null"
},
"fillOpacity": {
"signal": "datum.hasMouseOver ? datum.isMousedOver ? 1 : 0 : 1"
},
"opacity": {"field": "opacity"},
"fontSize": {"value": 14},
"align": {"value": "right"},
"baseline": {"value": "middle"},
"fill": {
"signal": "datum.hasMouseOver ? datum.isMousedOver ? '#111' : '#666' : '#666'"
}
}
}
}
]
},
{
"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": "gantt-rect-data"},
"interactive": {"signal": "true"},
"encode": {
"update": {
"tooltip": {
"signal": "data('gantt-item-tooltip-data')[0]"
},
"x": {
"signal": "panAndZoomMode ? (rowAnimationTEased === 1 && !panning) ? datum.x1 : 0 : 0"
},
"x2": {
"signal": "panAndZoomMode ? (rowAnimationTEased === 1 && !panning) ? datum.x2 : 0 : ganttDimensions.width"
},
"y": {"field": "y1"},
"y2": {"field": "y2"},
"fill": {"value": "transparent"},
"zindex": {
"signal": "datum.opacity === 0 ? -9999 : 1"
},
"cursor": {
"signal": "!isAnimating && datum.hasChildren && datum.level >= configInitialDepth ? 'pointer' : 'default'"
}
}
}
},
{
"name": "gantt-row-highlight-rect",
"type": "rect",
"from": {"data": "hierarchy-animation"},
"interactive": false,
"encode": {
"update": {
"x": {"signal": "0"},
"x2": {"signal": "ganttDimensions.width"},
"y": {"field": "y1"},
"y2": {"field": "y2"},
"fill": {"value": "rgba(237, 246, 252, 0.5)"},
"opacity": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.id ? datum.opacity : 0"
}
}
}
},
{
"name": "gantt-row-milestone-clickable-rect",
"type": "rect",
"from": {"data": "gantt-milestone-data"},
"interactive": true,
"encode": {
"update": {
"x": {
"signal": "panAndZoomMode ? datum.x1 : 0",
"offset": {"signal": "-configRow.rowHeight"}
},
"x2": {
"signal": "panAndZoomMode ? datum.x2 : ganttDimensions.width",
"offset": {"signal": "configRow.rowHeight"}
},
"y": {"field": "y1"},
"y2": {"field": "y2"},
"fill": {"value": "transparent"},
"zindex": {
"signal": "datum.opacity === 0 ? -9999 : 1"
},
"cursor": {
"signal": "!isAnimating && datum.hasChildren ? 'pointer' : 'default'"
}
}
}
},
{
"name": "gantt-click-stopper-rect",
"type": "rect",
"zindex": 9999,
"interactive": {"signal": "isAnimating"},
"encode": {
"update": {
"x": {"value": 0},
"y": {"value": 0},
"width": {"signal": "isAnimating ? width : 0"},
"height": {"signal": "isAnimating ? height : 0"},
"cursor": {"value": "default"},
"fill": {"value": "transparent"}
}
}
},
{
"name": "gantt-row-rect-background",
"description": "the invisible rect used for interactions for each row that spans across the visual horizontally",
"type": "rect",
"from": {"data": "gantt-rect-data"},
"interactive": false,
"encode": {
"update": {
"x": {"field": "x1"},
"x2": {"field": "x2"},
"y": {"field": "y1WithPadding"},
"y2": {"field": "y2WithPaddingParent"},
"fill": {"signal": "background"}
}
}
},
{
"name": "gantt-row-milestone-background",
"type": "symbol",
"from": {"data": "gantt-milestone-data"},
"interactive": false,
"encode": {
"update": {
"x": {"field": "x1", "offset": {"signal": "0"}},
"x2": {"field": "x2"},
"y": {
"field": "y1WithPadding",
"offset": {"signal": "+configRow.rowHeight*0.15"}
},
"y2": {"field": "y2WithPaddingParent"},
"size": {
"signal": "!inrange(datum.endDate,xDomain) ? 0 : 8*configRow.rowHeight"
},
"shape": {"value": "diamond"},
"fill": {"signal": "background"}
}
}
},
{
"name": "gantt-row-rect-container",
"type": "rect",
"from": {"data": "gantt-rect-data"},
"interactive": false,
"encode": {
"update": {
"x": {"field": "x1"},
"x2": {"field": "x2"},
"y": {"field": "y1WithPadding"},
"y2": {"field": "y2WithPaddingParent"},
"fill": {"field": "color"},
"fillOpacity": {"field": "opacity"},
"stroke": {"field": "color"},
"strokeWidth": {"value": 0.5},
"strokeOpacity": {
"signal": "datum.hasChildren ? datum.fullOpacity : 0"
}
}
}
},
{
"name": "gantt-row-milestone-symbol",
"type": "symbol",
"from": {"data": "gantt-milestone-data"},
"interactive": false,
"encode": {
"update": {
"x": {"field": "x1", "offset": {"signal": "0"}},
"x2": {"field": "x2"},
"y": {
"field": "y1WithPadding",
"offset": {"signal": "+configRow.rowHeight*0.15"}
},
"y2": {"field": "y2WithPaddingParent"},
"size": {
"signal": "!inrange(datum.endDate,xDomain) ? 0 : 8*configRow.rowHeight"
},
"shape": {"value": "diamond"},
"fill": {"field": "color"},
"opacity": {
"signal": "(datum.decimalPercentComplete === 1 ? 1.5 : 1) * datum.opacity"
}
}
}
},
{
"name": "gantt-row-rect-progress",
"type": "rect",
"from": {"data": "gantt-rect-data"},
"interactive": false,
"encode": {
"update": {
"x": {"field": "x1"},
"x2": {"field": "x2Progress"},
"y": {"field": "y1WithPadding"},
"y2": {"field": "y2WithPaddingParent"},
"fill": {"field": "color"},
"opacity": {"field": "opacity"}
}
}
},
{
"name": "gantt-row-rect-parent-left-border",
"type": "rect",
"from": {"data": "gantt-rect-data"},
"interactive": false,
"encode": {
"update": {
"x": {"field": "x1", "offset": -0.5},
"width": {
"signal": "inrange(datum.startDate,xDomain) && datum.hasChildren ? 2 : 0"
},
"y": {"field": "y1WithPadding"},
"y2": {"field": "y2WithPadding"},
"fill": {"field": "color"},
"opacity": {"field": "fullOpacity"}
}
}
},
{
"name": "gantt-row-rect-parent-right-border",
"type": "rect",
"from": {"data": "gantt-rect-data"},
"interactive": false,
"encode": {
"update": {
"x": {"field": "x2", "offset": -0.5},
"width": {
"signal": "inrange(datum.endDate,xDomain) && datum.hasChildren ? 2 : 0"
},
"y": {"field": "y1WithPadding"},
"y2": {"field": "y2WithPadding"},
"fill": {"field": "color"},
"opacity": {"field": "fullOpacity"}
}
}
},
{
"name": "gantt-row-label-background",
"type": "text",
"from": {"data": "gantt-label-data"},
"interactive": false,
"encode": {
"update": {
"text": {"field": "label"},
"baseline": {"value": "middle"},
"align": {"field": "align"},
"x": {"field": "x"},
"y": {
"field": "y1",
"offset": {"signal": "configRow.rowHeight/2"}
},
"fill": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.id ? 'rgba(237, 246, 252, 0.5)' : background"
},
"stroke": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.id ? 'rgba(237, 246, 252, 0.5)' : background"
},
"strokeWidth": {"value": 2},
"opacity": {"signal": "datum.fullOpacity"}
}
}
},
{
"name": "gantt-row-label",
"type": "text",
"from": {"data": "gantt-row-label-background"},
"interactive": false,
"encode": {
"update": {
"text": {"field": "text"},
"baseline": {"field": "baseline"},
"align": {"field": "align"},
"x": {"field": "x"},
"y": {"field": "y"},
"fill": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.datum.id ? '#333' : '#777'"
},
"fontWeight": {
"signal": "isValid(mouseoverNodeDatum) && mouseoverNodeDatum.id === datum.datum.id ? 600 : 400"
},
"opacity": {"signal": "datum.datum.fullOpacity"}
}
}
}
]
}
]
},
{
"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",
"from": {"data": "gantt-canvas-static-group"},
"interactive": false,
"encode": {
"update": {
"x": {"signal": "ganttDimensions.width"},
"y": {
"signal": "datum.bounds.y2+configDateGranularitySlider.track.height*2+10"
},
"height": {
"signal": "configDateGranularitySlider.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-configDateGranularitySlider.innerPadding/2"
}
},
"update": {
"y": {
"signal": "(datum.bounds.y2-datum.bounds.y1)/2+1"
},
"align": {"value": "right"},
"baseline": {"value": "middle"},
"text": {"signal": "'Reset'"},
"fontSize": {
"signal": "configDateGranularitySlider.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-configDateGranularitySlider.innerPadding-configDateGranularitySlider.track.width"
}
},
"update": {
"y": {
"signal": "(datum.bounds.y2-datum.bounds.y1)/2-configDateGranularitySlider.track.height/4"
},
"height": {
"signal": "configDateGranularitySlider.track.height"
},
"width": {
"signal": "configDateGranularitySlider.track.width"
},
"cornerRadius": {
"signal": "configDateGranularitySlider.track.cornerRadius"
},
"fill": {
"signal": "configDateGranularitySlider.track.fill"
},
"stroke": {
"signal": "configDateGranularitySlider.track.stroke"
},
"strokeWidth": {
"signal": "configDateGranularitySlider.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": "configDateGranularitySlider.track.height"
},
"x": {"signal": "datum.bounds.x1"},
"y": {"signal": "datum.bounds.y1+1"},
"width": {
"signal": "xDomainZoomPercentage*configDateGranularitySlider.track.width"
},
"cornerRadius": {
"signal": "configDateGranularitySlider.track.cornerRadius"
},
"fill": {
"signal": "configDateGranularitySlider.progress.fill"
},
"fillOpacity": {
"signal": "configDateGranularitySlider.progress.fillOpacity"
},
"stroke": {
"signal": "configDateGranularitySlider.track.stroke"
},
"strokeWidth": {
"signal": "configDateGranularitySlider.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+(configDateGranularitySlider.track.height)/2"
},
"innerRadius": {"value": 0},
"outerRadius": {
"signal": "configDateGranularitySlider.track.height*0.9"
},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "configDateGranularitySlider.handle.outerStroke || '#BBB'"
},
"strokeWidth": {
"signal": "configDateGranularitySlider.handle.outerStrokeWidth"
},
"fill": {
"signal": "configDateGranularitySlider.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": "configDateGranularitySlider.handle.innerStroke"
},
"fill": {
"signal": "configDateGranularitySlider.handle.innerFill || '#999'"
}
},
"update": {"x": {"signal": "datum.x"}}
}
}
]
},
{
"name": "text-date-unit-label",
"type": "text",
"from": {"data": "group-date-granularity-slider"},
"encode": {
"update": {
"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": "-configDateGranularitySlider.track.cornerRadius*2"
}
},
"update": {
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {
"signal": "configDateGranularitySlider.label.text || ''"
},
"baseline": {"value": "middle"},
"font": {
"signal": "configDateGranularitySlider.label.font"
},
"fontSize": {
"signal": "configDateGranularitySlider.label.fontSize"
},
"fontStyle": {
"signal": "configDateGranularitySlider.label.fontStyle"
},
"align": {"value": "right"},
"fill": {
"signal": "configDateGranularitySlider.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": "configDateGranularitySlider.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 - configDateGranularitySlider.track.height*3"
},
"x2": {"signal": "panAndZoomMode ? 0 : datum.bounds.x2"},
"height": {
"signal": "(datum.bounds.y2-datum.bounds.y1) + (ganttHorizontalScrollMapMouseDown ? ganttDimensions.height + ganttPosition.dy + (datum.bounds.y2-datum.bounds.y1) +configDateGranularitySlider.track.height : 0)"
},
"fillOpacity": {"value": 0},
"cursor": {
"signal": "ganttHorizontalScrollMapMouseOver ? 'pointer' : 'default'"
}
}
}
}
]
},
{
"name": "vertical-scrollbar-group",
"description": "the group of marks that make up the vertical scrollbar",
"type": "group",
"interactive": true,
"encode": {
"update": {
"y": {
"signal": "verticalScrollbarMouseDown ? -2*ganttPosition.dy: 0"
},
"x": {
"signal": "verticalScrollbarMouseDown ? 0 : ganttPosition.x+ganttDimensions.width"
},
"width": {
"signal": "actualHeight > adjustedHeight && configVerticalScrollbar.enabled ? verticalScrollbarMouseDown ? ganttPosition.x+ganttDimensions.width+configVerticalScrollbar.track.width : configVerticalScrollbar.track.width : 0"
},
"height": {
"signal": "actualHeight > adjustedHeight && configVerticalScrollbar.enabled ? ((verticalScrollbarMouseDown ? 4*ganttPosition.dy : 0) + ganttDimensions.height) : 0"
},
"fill": {"value": "transparent"},
"cursor": {"signal": "panAndZoomMode ? 'default' : 'pointer'"},
"zindex": {"value": 999}
}
},
"marks": [
{
"name": "rect_verticalScrollbar_track",
"description": "the track for the scrollbar",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"y": {
"signal": "verticalScrollbarMouseDown ? 2*ganttPosition.dy: 0"
},
"x": {
"signal": "verticalScrollbarMouseDown ? ganttPosition.x+ganttDimensions.width : 0"
},
"width": {
"signal": "configVerticalScrollbar.enabled ? configVerticalScrollbar.track.width : 0"
},
"height": {
"signal": "configVerticalScrollbar.enabled ? configVerticalScrollbar.track.height : 0"
},
"fill": {"signal": "configVerticalScrollbar.track.fill"},
"fillOpacity": {"signal": "panAndZoomMode ? 0.15 : 1"},
"stroke": {"signal": "configBorders.stroke"},
"strokeWidth": {"signal": "configBorders.strokeWidth"}
}
}
},
{
"name": "rect_verticalScrollbar_handle",
"description": "the handle for the scrollbar",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "verticalScrollbarMouseDown ? ganttPosition.x+ganttDimensions.width : 0"
},
"width": {
"signal": "configVerticalScrollbar.enabled ? configVerticalScrollbar.track.width : 0"
},
"y": {
"signal": "scale('scaleScrollHandleY', verticalScrollPercentage)-configVerticalScrollbar.handle.height"
},
"dy": {
"signal": "verticalScrollbarMouseDown ? 2*ganttPosition.dy: 0"
},
"y2": {
"signal": "scale('scaleScrollHandleY', verticalScrollPercentage)"
},
"fill": {
"signal": "verticalScrollbarMouseOver || verticalScrollbarMouseDown ? configVerticalScrollbar.handle.hover.fill : configVerticalScrollbar.handle.fill"
},
"fillOpacity": {"signal": "panAndZoomMode ? 0.15 : 1"}
}
}
}
]
},
{
"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"}
}
}
}
]
}
]
},
{
"name": "settings-outer-group",
"type": "group",
"signals": [
{
"name": "settingsAnimT",
"init": "1",
"on": [
{
"events": {"signal": "timer"},
"update": "settingsIndicatorClicked ? clamp((timer-settingsMenuAnimStart || now())/(configAnimationDuration.settingsMenu),0,1) : 1-clamp((timer-settingsMenuAnimStart || now())/(configAnimationDuration.settingsMenu),0,1)"
}
]
},
{
"name": "settingsAnimT2",
"init": "1",
"on": [
{
"events": {"signal": "timer"},
"update": "settingsIndicatorClicked ? clamp(((timer-configAnimationDuration.settingsMenu)-settingsMenuAnimStart || now())/(configAnimationDuration.settingsMenu),0,1) : 1-clamp(((timer)-settingsMenuAnimStart || now())/(configAnimationDuration.settingsMenu/2),0,1)"
}
]
},
{
"name": "settingsAnimTEased",
"update": "settingsAnimT < 0.5 ? 4*pow(settingsAnimT,3) : 1 - pow(-2*settingsAnimT + 2, 3)/2"
},
{
"name": "settingsAnimTEased2",
"update": "settingsAnimT2 < 0.5 ? 4*pow(settingsAnimT2,3) : 1 - pow(-2*settingsAnimT2 + 2, 3)/2"
},
{
"name": "isAnimating",
"init": "false",
"on": [{"events": {"signal": "timer"}, "update": "settingsAnimT < 1"}]
}
],
"marks": [
{
"name": "settings-group",
"type": "group",
"data": [
{
"name": "group-everything-filtered",
"on": [
{
"trigger": "settingsIndicatorClickedCount",
"remove": false,
"insert": "{timestamp: settingsMenuAnimStart}"
},
{
"trigger": "!settingsIndicatorClicked && (settingsAnimT === 0)",
"remove": true
}
],
"transform": [
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "timestamp", "order": "descending"},
"as": ["index"]
},
{
"type": "filter",
"expr": "datum.index === 1 && (settingsAnimT > 0)"
}
]
}
],
"marks": [
{
"name": "settings-group-marks",
"type": "group",
"from": {"data": "group-everything-filtered"},
"key": "timestamp",
"encode": {
"update": {
"x": {"signal": "data('group-everything')[0].bounds.x1"},
"x2": {"signal": "data('group-everything')[0].bounds.x2"},
"y": {"signal": "data('group-everything')[0].bounds.y1"},
"y2": {"signal": "data('group-everything')[0].bounds.y2"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "settings-backdrop",
"type": "rect",
"interactive": true,
"encode": {
"update": {
"width": {
"signal": "data('group-everything')[0].bounds.x2-data('group-everything')[0].bounds.x1"
},
"height": {
"signal": "data('group-everything')[0].bounds.y2-data('group-everything')[0].bounds.y1"
},
"fill": {"value": "#fff"},
"fillOpacity": {"signal": "0.8*settingsAnimTEased"},
"cursor": {"value": "pointer"}
}
}
},
{
"name": "settings-menu-background",
"type": "rect",
"interactive": true,
"encode": {
"update": {
"x": {
"signal": "data('group-everything')[0].bounds.x2-settingsAnimTEased*200"
},
"y": {"signal": "1"},
"width": {"signal": "settingsAnimTEased*200"},
"height": {
"signal": "(data('group-everything')[0].bounds.y2-data('group-everything')[0].bounds.y1)",
"offset": -2.5
},
"fill": {"value": "#fff"},
"stroke": {"value": "#fff"},
"strokeWidth": {"value": 4}
}
}
},
{
"name": "settings-indicator",
"type": "text",
"interactive": false,
"from": {"data": "settings-menu-background"},
"encode": {
"update": {
"x": {"signal": "datum.bounds.x2-20"},
"y": {"signal": "datum.bounds.y1+3.5"},
"text": {"value": "⮞"},
"baseline": {"value": "top"},
"fontSize": {"value": 14},
"fontWeight": {"value": 600},
"fill": {
"signal": "settingsIndicatorMouseover ? '#555' : '#bbb' "
},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "settings-indicator-rect",
"type": "rect",
"interactive": true,
"from": {"data": "settings-indicator"},
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1"},
"x2": {"signal": "datum.bounds.x2"},
"y": {"signal": "datum.bounds.y1"},
"y2": {"signal": "datum.bounds.y2"},
"fill": {"value": "transparent"},
"cursor": {"value": "pointer"}
}
}
},
{
"name": "settings-menu-header-border",
"type": "rect",
"interactive": false,
"from": {"data": "settings-menu-background"},
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1", "offset": 15},
"y": {"signal": "ganttPosition.dy"},
"width": {"value": 1.5},
"y2": {
"signal": "datum.bounds.y2",
"offset": {"signal": "-ganttPosition.dy"}
},
"fill": {"value": "#ddd"}
}
}
},
{
"name": "settings-header-label",
"type": "text",
"from": {"data": "settings-indicator"},
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "data('settings-menu-background')[0].x + data('settings-menu-background')[0].width/2"
},
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {"value": "Settings"},
"fontStyle": {"value": "italic"},
"fontSize": {"value": 17},
"align": {"value": "center"},
"baseline": {"value": "middle"},
"fill": {"value": "#444"},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "menu-items-group",
"type": "group",
"encode": {
"update": {
"x": {
"signal": "isValid(data('settings-menu-header-border')[0]) ? data('settings-menu-header-border')[0].bounds.x2 : 0",
"offset": 10
},
"y": {
"signal": "isValid(data('settings-menu-header-border')[0]) ? data('settings-header-label')[0].bounds.y2 : 0",
"offset": 25
}
}
},
"marks": [
{
"name": "group-show-details",
"type": "group",
"interactive": false,
"encode": {
"update": {
"width": {"signal": "settingsAnimTEased*175"},
"height": {
"signal": "!configShowDetails.enabled ? 0 : 2*configShowDetails.track.height"
},
"fill": {"value": "transparent"},
"clip": {"value": true}
}
},
"signals": [
{
"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 ? (configShowDetails.track.width - configShowDetails.track.height*0.9) : (configShowDetails.track.height*0.9)",
"on": [
{
"events": "@show-details-interactive-rect:click",
"update": "!showDetails ? (configShowDetails.track.width - configShowDetails.track.height*0.9) : (configShowDetails.track.height*0.9)"
}
]
},
{
"name": "showDetailsTarget",
"init": "showDetails ? (configShowDetails.track.width - configShowDetails.track.height*0.9) : (configShowDetails.track.height*0.9)",
"on": [
{
"events": "@show-details-interactive-rect:click",
"update": "showDetails ? (configShowDetails.track.width - configShowDetails.track.height*0.9) : (configShowDetails.track.height*0.9)"
}
]
}
],
"marks": [
{
"name": "show-details-track-rect",
"description": "the track for the toggle control",
"type": "rect",
"from": {"data": "settings-menu-background"},
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "datum.bounds.x2-datum.bounds.x1-configShowDetails.track.width",
"offset": {"signal": "-10-20-4"}
},
"y": {
"signal": "configShowDetails.track.height/2"
},
"height": {
"signal": "configShowDetails.track.height"
},
"width": {
"signal": "configShowDetails.track.width"
},
"cornerRadius": {
"signal": "configShowDetails.track.cornerRadius"
},
"fill": {
"signal": "(showDetails && showDetailsClick.tEased > 0.9) || (!showDetails && showDetailsClick.tEased < 0.1)? configShowDetails.on.fill : configShowDetails.track.fill"
},
"stroke": {
"signal": "configShowDetails.track.stroke"
},
"strokeWidth": {
"signal": "configShowDetails.track.strokeWidth"
},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "show-details-label",
"description": "the title for the toggle control",
"type": "text",
"from": {"data": "show-details-track-rect"},
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "datum.bounds.x1",
"offset": {
"signal": "-configShowDetails.label.dx"
}
},
"y": {
"signal": "datum.bounds.y1 + (datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {
"signal": "configShowDetails.label.text"
},
"baseline": {"value": "middle"},
"font": {
"signal": "configShowDetails.label.font"
},
"fontSize": {
"signal": "configShowDetails.label.fontSize"
},
"fontStyle": {
"signal": "configShowDetails.label.fontStyle"
},
"align": {"value": "right"},
"fill": {
"signal": "configShowDetails.label.fill"
},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "group-show-details-toggle-outer-arc",
"type": "group",
"from": {"data": "show-details-track-rect"},
"interactive": false,
"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+configShowDetails.track.height/2"
}
},
"update": {
"opacity": {"signal": "settingsAnimTEased2"}
}
},
"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": "configShowDetails.track.height*0.9"
},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "configShowDetails.handle.stroke || '#BBB'"
},
"strokeWidth": {
"signal": "configShowDetails.handle.strokeWidth"
},
"fill": {
"signal": "configShowDetails.handle.fill || '#fff'"
}
},
"update": {
"x": {"signal": "datum.x"},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "show-details-interactive-rect",
"type": "rect",
"from": {"data": "show-details-track-rect"},
"interactive": true,
"encode": {
"update": {
"y": {
"signal": "datum.bounds.y1-(configShowDetails.track.height*0.5)"
},
"y2": {
"signal": "datum.bounds.y2+configShowDetails.track.height*0.5"
},
"x": {
"signal": "-(datum.bounds.x2-datum.bounds.x1)"
},
"x2": {"signal": "datum.bounds.x2"},
"fill": {"value": "transparent"},
"tooltip": {
"signal": "configShowDetails.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",
"from": {"data": "group-show-details"},
"interactive": false,
"encode": {
"update": {
"y": {
"signal": "datum.bounds.y2",
"offset": {
"signal": "7.5*(!configTogglepanAndZoomMode.enabled ? 0 : 1)"
}
},
"width": {"signal": "settingsAnimTEased*175"},
"height": {
"signal": "!configTogglepanAndZoomMode.enabled ? 0 : 2*configTogglepanAndZoomMode.track.height"
},
"fill": {"value": "transparent"},
"clip": {"value": true}
}
},
"signals": [
{
"name": "panAndZoomModeClick",
"init": "{start: now(), end: now(), t: 1, tEased: 1}",
"on": [
{
"events": [{"signal": "panAndZoomMode"}],
"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": "panAndZoomModeStart",
"init": "!panAndZoomMode ? (configTogglepanAndZoomMode.track.width - configTogglepanAndZoomMode.track.height*0.9) : (configTogglepanAndZoomMode.track.height*0.9)",
"on": [
{
"events": "@pan-and-zoom-mode-interactive-rect:click",
"update": "!panAndZoomMode ? (configTogglepanAndZoomMode.track.width - configTogglepanAndZoomMode.track.height*0.9) : (configTogglepanAndZoomMode.track.height*0.9)"
}
]
},
{
"name": "panAndZoomModeTarget",
"init": "panAndZoomMode ? (configTogglepanAndZoomMode.track.width - configTogglepanAndZoomMode.track.height*0.9) : (configTogglepanAndZoomMode.track.height*0.9)",
"on": [
{
"events": "@pan-and-zoom-mode-interactive-rect:click",
"update": "panAndZoomMode ? (configTogglepanAndZoomMode.track.width - configTogglepanAndZoomMode.track.height*0.9) : (configTogglepanAndZoomMode.track.height*0.9)"
}
]
}
],
"marks": [
{
"name": "pan-and-zoom-mode-track-rect",
"description": "the track for the toggle control",
"type": "rect",
"from": {"data": "settings-menu-background"},
"interactive": true,
"encode": {
"update": {
"x": {
"signal": "datum.bounds.x2-datum.bounds.x1-configTogglepanAndZoomMode.track.width",
"offset": {"signal": "-10-20-4"}
},
"y": {
"signal": "configTogglepanAndZoomMode.track.height/2"
},
"height": {
"signal": "configTogglepanAndZoomMode.track.height"
},
"width": {
"signal": "configTogglepanAndZoomMode.track.width"
},
"cornerRadius": {
"signal": "configTogglepanAndZoomMode.track.cornerRadius"
},
"fill": {
"signal": "(panAndZoomMode && panAndZoomModeClick.tEased > 0.9) || (!panAndZoomMode && panAndZoomModeClick.tEased < 0.1) ? configTogglepanAndZoomMode.on.fill : configTogglepanAndZoomMode.track.fill"
},
"stroke": {
"signal": "configTogglepanAndZoomMode.track.stroke"
},
"strokeWidth": {
"signal": "configTogglepanAndZoomMode.track.strokeWidth"
},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "pan-and-zoom-mode-label",
"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",
"offset": {
"signal": "-configTogglepanAndZoomMode.label.dx"
}
},
"y": {
"signal": "datum.bounds.y1 + (datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {
"signal": "configTogglepanAndZoomMode.label.text"
},
"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"
},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "group-show-details-toggle-outer-arc",
"type": "group",
"from": {"data": "pan-and-zoom-mode-track-rect"},
"interactive": false,
"transform": [
{
"type": "formula",
"expr": "panAndZoomModeClick.tEased",
"as": "tEased"
},
{
"type": "formula",
"expr": "panAndZoomModeStart + (panAndZoomModeTarget - panAndZoomModeStart) * panAndZoomModeClick.tEased",
"as": "x"
}
],
"encode": {
"enter": {
"x": {"signal": "datum.bounds.x1"},
"y": {
"signal": "datum.bounds.y1+configTogglepanAndZoomMode.track.height/2"
}
},
"update": {
"opacity": {"signal": "settingsAnimTEased2"}
}
},
"marks": [
{
"name": "show-details-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"},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "pan-and-zoom-mode-interactive-rect",
"type": "rect",
"from": {"data": "pan-and-zoom-mode-track-rect"},
"interactive": true,
"encode": {
"update": {
"y": {
"signal": "datum.bounds.y1-(configTogglepanAndZoomMode.track.height*0.5)"
},
"y2": {
"signal": "datum.bounds.y2+configTogglepanAndZoomMode.track.height*0.5"
},
"x": {
"signal": "-(datum.bounds.x2-datum.bounds.x1)"
},
"x2": {"signal": "datum.bounds.x2"},
"fill": {"value": "transparent"},
"tooltip": {
"signal": "configTogglepanAndZoomMode.tooltip.object"
},
"cursor": {"value": "pointer"}
}
}
}
]
}
]
},
{
"name": "show-bar-labels-group",
"type": "group",
"from": {"data": "group-pan-and-zoom-mode"},
"encode": {
"update": {
"y": {
"signal": "datum.bounds.y2",
"offset": {
"signal": "7.5*(!hasLabels || !configShowBarLabels.enabled ? 0 : 1)"
}
},
"width": {"signal": "settingsAnimTEased*175"},
"height": {
"signal": "!hasLabels || !configShowBarLabels.enabled ? 0 : 2*configShowBarLabels.track.height"
},
"fill": {"value": "transparent"},
"clip": {"value": true}
}
},
"signals": [
{
"name": "showBarLabelsClick",
"init": "{start: now(), end: now(), t: 1, tEased: 1}",
"on": [
{
"events": [{"signal": "showBarLabels"}],
"update": "{start: now(), end: now()+250, t:0, tEased: 0}"
},
{
"events": {"signal": "timer"},
"update": "showBarLabelsClick.t < 1 ? {start: showBarLabelsClick.start, end: showBarLabelsClick.end, t:(now()-showBarLabelsClick.start)/(showBarLabelsClick.end-showBarLabelsClick.start), tEased: clamp(showBarLabelsClick.t < 0.5 ? 4 * pow(showBarLabelsClick.t, 3) : 1 - pow(-2 * showBarLabelsClick.t + 2, 3) / 2, 0,1)} : {start: showBarLabelsClick.start, end: showBarLabelsClick.end, t: 1, tEased: 1}"
}
]
},
{
"name": "showBarLabelsStart",
"init": "!showBarLabels ? (configShowBarLabels.track.width - configShowBarLabels.track.height*0.9) : (configShowBarLabels.track.height*0.9)",
"on": [
{
"events": "@show-bar-labels-interactive-rect:click",
"update": "!showBarLabels ? (configShowBarLabels.track.width - configShowBarLabels.track.height*0.9) : (configShowBarLabels.track.height*0.9)"
}
]
},
{
"name": "showBarLabelsTarget",
"init": "showBarLabels ? (configShowBarLabels.track.width - configShowBarLabels.track.height*0.9) : (configShowBarLabels.track.height*0.9)",
"on": [
{
"events": "@show-bar-labels-interactive-rect:click",
"update": "showBarLabels ? (configShowBarLabels.track.width - configShowBarLabels.track.height*0.9) : (configShowBarLabels.track.height*0.9)"
}
]
}
],
"marks": [
{
"name": "show-bar-labels-track-rect",
"description": "the track for the toggle control",
"type": "rect",
"from": {"data": "settings-menu-background"},
"interactive": true,
"encode": {
"update": {
"x": {
"signal": "datum.bounds.x2-datum.bounds.x1-configShowBarLabels.track.width",
"offset": {"signal": "-10-20-4"}
},
"y": {
"signal": "configShowBarLabels.track.height/2"
},
"height": {
"signal": "configShowBarLabels.track.height"
},
"width": {
"signal": "configShowBarLabels.track.width"
},
"cornerRadius": {
"signal": "configShowBarLabels.track.cornerRadius"
},
"fill": {
"signal": "(showBarLabels && showBarLabelsClick.tEased > 0.9) || (!showBarLabels && showBarLabelsClick.tEased < 0.1) ? configShowBarLabels.on.fill : configShowBarLabels.track.fill"
},
"stroke": {
"signal": "configShowBarLabels.track.stroke"
},
"strokeWidth": {
"signal": "configShowBarLabels.track.strokeWidth"
},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "show-bar-labels-label",
"description": "the title for the toggle control",
"type": "text",
"from": {"data": "show-bar-labels-track-rect"},
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "datum.bounds.x1",
"offset": {
"signal": "-configShowBarLabels.label.dx"
}
},
"y": {
"signal": "datum.bounds.y1 + (datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {
"signal": "configShowBarLabels.label.text"
},
"baseline": {"value": "middle"},
"font": {
"signal": "configShowBarLabels.label.font"
},
"fontSize": {
"signal": "configShowBarLabels.label.fontSize"
},
"fontStyle": {
"signal": "configShowBarLabels.label.fontStyle"
},
"align": {"value": "right"},
"fill": {
"signal": "configShowBarLabels.label.fill"
},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "group-show-bar-labels-toggle-outer-arc",
"type": "group",
"from": {"data": "show-bar-labels-track-rect"},
"interactive": false,
"transform": [
{
"type": "formula",
"expr": "showBarLabelsClick.tEased",
"as": "tEased"
},
{
"type": "formula",
"expr": "showBarLabelsStart + (showBarLabelsTarget - showBarLabelsStart) * showBarLabelsClick.tEased",
"as": "x"
}
],
"encode": {
"enter": {
"x": {"signal": "datum.bounds.x1"},
"y": {
"signal": "datum.bounds.y1+configShowBarLabels.track.height/2"
}
},
"update": {
"opacity": {"signal": "settingsAnimTEased2"}
}
},
"marks": [
{
"name": "show-bar-labels-toggle-outer-arc",
"description": "the circle mark that serves as the the 'toggle handle'",
"from": {"data": "show-bar-labels-track-rect"},
"type": "arc",
"interactive": false,
"encode": {
"enter": {
"innerRadius": {"value": 0},
"outerRadius": {
"signal": "configShowBarLabels.track.height*0.9"
},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "configShowBarLabels.handle.stroke || '#BBB'"
},
"strokeWidth": {
"signal": "configShowBarLabels.handle.strokeWidth"
},
"fill": {
"signal": "configShowBarLabels.handle.fill || '#fff'"
}
},
"update": {
"x": {"signal": "datum.x"},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "show-bar-labels-interactive-rect",
"type": "rect",
"from": {"data": "show-bar-labels-track-rect"},
"interactive": true,
"encode": {
"update": {
"y": {
"signal": "datum.bounds.y1-(configShowBarLabels.track.height*0.5)"
},
"y2": {
"signal": "datum.bounds.y2+configShowBarLabels.track.height*0.5"
},
"x": {
"signal": "-(datum.bounds.x2-datum.bounds.x1)"
},
"x2": {"signal": "datum.bounds.x2"},
"fill": {"value": "transparent"},
"tooltip": {
"signal": "configShowBarLabels.tooltip.text"
},
"cursor": {"value": "pointer"}
}
}
}
]
}
]
},
{
"name": "show-dependencies-group",
"type": "group",
"from": {"data": "show-bar-labels-group"},
"encode": {
"update": {
"y": {
"signal": "datum.bounds.y2",
"offset": {
"signal": "7.5*(!hasDependencies || !configShowDependencies.enabled ? 0 : 1)"
}
},
"width": {"signal": "settingsAnimTEased*175"},
"height": {
"signal": "!hasDependencies || !configShowDependencies.enabled ? 0 : 2*configShowDependencies.track.height"
},
"fill": {"value": "transparent"},
"clip": {"value": true}
}
},
"signals": [
{
"name": "showDependenciesClick",
"init": "{start: now(), end: now(), t: 1, tEased: 1}",
"on": [
{
"events": [{"signal": "showDependencies"}],
"update": "{start: now(), end: now()+250, t:0, tEased: 0}"
},
{
"events": {"signal": "timer"},
"update": "showDependenciesClick.t < 1 ? {start: showDependenciesClick.start, end: showDependenciesClick.end, t:(now()-showDependenciesClick.start)/(showDependenciesClick.end-showDependenciesClick.start), tEased: clamp(showDependenciesClick.t < 0.5 ? 4 * pow(showDependenciesClick.t, 3) : 1 - pow(-2 * showDependenciesClick.t + 2, 3) / 2, 0,1)} : {start: showDependenciesClick.start, end: showDependenciesClick.end, t: 1, tEased: 1}"
}
]
},
{
"name": "showDependenciesStart",
"init": "!showDependencies ? (configShowDependencies.track.width - configShowDependencies.track.height*0.9) : (configShowDependencies.track.height*0.9)",
"on": [
{
"events": "@show-dependencies-interactive-rect:click",
"update": "!showDependencies ? (configShowDependencies.track.width - configShowDependencies.track.height*0.9) : (configShowDependencies.track.height*0.9)"
}
]
},
{
"name": "showDependenciesTarget",
"init": "showDependencies ? (configShowDependencies.track.width - configShowDependencies.track.height*0.9) : (configShowDependencies.track.height*0.9)",
"on": [
{
"events": "@show-dependencies-interactive-rect:click",
"update": "showDependencies ? (configShowDependencies.track.width - configShowDependencies.track.height*0.9) : (configShowDependencies.track.height*0.9)"
}
]
}
],
"marks": [
{
"name": "show-dependencies-track-rect",
"description": "the track for the toggle control",
"type": "rect",
"from": {"data": "settings-menu-background"},
"interactive": true,
"encode": {
"update": {
"x": {
"signal": "datum.bounds.x2-datum.bounds.x1-configShowDependencies.track.width",
"offset": {"signal": "-10-20-4"}
},
"y": {
"signal": "configShowDependencies.track.height/2"
},
"height": {
"signal": "configShowDependencies.track.height"
},
"width": {
"signal": "configShowDependencies.track.width"
},
"cornerRadius": {
"signal": "configShowDependencies.track.cornerRadius"
},
"fill": {
"signal": "(showDependencies && showDependenciesClick.tEased > 0.9) || (!showDependencies && showDependenciesClick.tEased < 0.1) ? configShowDependencies.on.fill : configShowDependencies.track.fill"
},
"stroke": {
"signal": "configShowDependencies.track.stroke"
},
"strokeWidth": {
"signal": "configShowDependencies.track.strokeWidth"
},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "show-dependencies-label",
"description": "the title for the toggle control",
"type": "text",
"from": {"data": "show-dependencies-track-rect"},
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "datum.bounds.x1",
"offset": {
"signal": "-configShowDependencies.label.dx"
}
},
"y": {
"signal": "datum.bounds.y1 + (datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {
"signal": "configShowDependencies.label.text"
},
"baseline": {"value": "middle"},
"font": {
"signal": "configShowDependencies.label.font"
},
"fontSize": {
"signal": "configShowDependencies.label.fontSize"
},
"fontStyle": {
"signal": "configShowDependencies.label.fontStyle"
},
"align": {"value": "right"},
"fill": {
"signal": "configShowDependencies.label.fill"
},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "group-show-dependencies-toggle-outer-arc",
"type": "group",
"from": {"data": "show-dependencies-track-rect"},
"interactive": false,
"transform": [
{
"type": "formula",
"expr": "showDependenciesClick.tEased",
"as": "tEased"
},
{
"type": "formula",
"expr": "showDependenciesStart + (showDependenciesTarget - showDependenciesStart) * showDependenciesClick.tEased",
"as": "x"
}
],
"encode": {
"enter": {
"x": {"signal": "datum.bounds.x1"},
"y": {
"signal": "datum.bounds.y1+configShowDependencies.track.height/2"
}
},
"update": {
"opacity": {"signal": "settingsAnimTEased2"}
}
},
"marks": [
{
"name": "show-dependencies-toggle-outer-arc",
"description": "the circle mark that serves as the the 'toggle handle'",
"from": {"data": "show-dependencies-track-rect"},
"type": "arc",
"interactive": false,
"encode": {
"enter": {
"innerRadius": {"value": 0},
"outerRadius": {
"signal": "configShowDependencies.track.height*0.9"
},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "configShowDependencies.handle.stroke || '#BBB'"
},
"strokeWidth": {
"signal": "configShowDependencies.handle.strokeWidth"
},
"fill": {
"signal": "configShowDependencies.handle.fill || '#fff'"
}
},
"update": {
"x": {"signal": "datum.x"},
"opacity": {"signal": "settingsAnimTEased2"}
}
}
},
{
"name": "show-dependencies-interactive-rect",
"type": "rect",
"from": {"data": "show-dependencies-track-rect"},
"interactive": true,
"encode": {
"update": {
"y": {
"signal": "datum.bounds.y1-(configShowDependencies.track.height*0.5)"
},
"y2": {
"signal": "datum.bounds.y2+configShowDependencies.track.height*0.5"
},
"x": {
"signal": "-(datum.bounds.x2-datum.bounds.x1)"
},
"x2": {"signal": "datum.bounds.x2"},
"fill": {"value": "transparent"},
"tooltip": {
"signal": "configShowDependencies.tooltip.text"
},
"cursor": {"value": "pointer"}
}
}
}
]
}
]
}
]
}
]
}
]
}
]
}
],
"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
},
{
"name": "scaleScrollHandleY",
"type": "linear",
"domain": [0, {"signal": "(actualHeight-adjustedHeight)/actualHeight"}],
"range": {
"signal": "[(verticalScrollbarMouseDown ? 2*ganttPosition.dy: 0)+configVerticalScrollbar.handle.height, (verticalScrollbarMouseDown ? 2*ganttPosition.dy: 0)+configVerticalScrollbar.track.height]"
},
"clamp": true
}
],
"data": [
{
"name": "dataset",
"url": "https://raw.githubusercontent.com/Giammaria/PublicFiles/refs/heads/master/data/20251027_hierarchical_gantt_dataset.json?version=6",
"format": {
"parse": {
"id": "number",
"parentId": "number",
"name": "string",
"startDate": "date",
"endDate": "date",
"decimalPercentComplete": "number",
"dependencyId": "string",
"color": "string"
}
},
"transform": []
},
{
"name": "dataset-formatted",
"source": "dataset",
"transform": [
{
"type": "formula",
"expr": "isValid(datum.color) ? datum.color : configRow.defaultFill",
"as": "color"
},
{
"type": "formula",
"expr": "toNumber(datum.startDate)",
"as": "startDate"
},
{"type": "formula", "expr": "toNumber(datum.endDate)", "as": "endDate"},
{
"type": "formula",
"expr": "!isValid(datum['startDate']) ? null : datum['endDate']",
"as": "endDate"
},
{
"type": "formula",
"expr": "datum.dependencyId && datum.dependencyId!=='null' ? split(replace(datum.dependencyId,' ',''), ',') : []",
"as": "dependencies"
},
{"type": "window", "ops": ["row_number"], "as": ["defaultSort"]},
{"type": "formula", "expr": "sortField", "as": "sortField"},
{"type": "formula", "expr": "datum[sortField]", "as": "sortRaw"},
{
"type": "formula",
"expr": "!isValid(datum.sortRaw) ? null : isDate(datum.sortRaw) ? toNumber(datum.sortRaw) : isNumber(datum.sortRaw) ? datum.sortRaw : lower(toString(datum.sortRaw))",
"as": "sortKey"
},
{
"type": "formula",
"expr": "isValid(datum.sortKey) ? 0 : 1",
"as": "sortNull"
},
{
"type": "window",
"groupby": ["parentId"],
"sort": {
"field": ["sortNull", "sortKey", "defaultSort"],
"order": ["ascending", {"signal": "sortOrder"}, "ascending"]
},
"ops": ["row_number"],
"as": ["siblingRank"]
},
{
"type": "formula",
"expr": "pad(toString(datum.siblingRank), 6, '0', 'left')",
"as": "siblingRankPad"
},
{"type": "stratify", "key": "id", "parentKey": "parentId"},
{
"type": "formula",
"expr": "configInitialDepth",
"as": "configInitialDepth"
},
{
"type": "formula",
"expr": "{id: datum.id, parentId: datum.parentId}",
"as": "idObj"
}
]
},
{
"name": "dataset-formatted-sorted",
"source": "dataset-formatted",
"transform": [
{
"type": "formula",
"expr": "join(pluck(reverse(treeAncestors('dataset-formatted', datum.id)), 'siblingRankPad'), '.')",
"as": "sortPath"
},
{
"type": "window",
"ops": ["row_number"],
"sort": {
"field": ["sortPath", "defaultSort"],
"order": ["ascending", "ascending"]
},
"as": ["sort"]
}
]
},
{
"name": "child-counts",
"source": "dataset",
"transform": [
{
"type": "aggregate",
"groupby": ["parentId"],
"ops": ["count"],
"as": ["childCount"]
}
]
},
{
"name": "expandable-node-ids",
"source": "dataset",
"transform": [
{
"type": "lookup",
"from": "child-counts",
"key": "parentId",
"fields": ["id"],
"as": ["childInfo"]
},
{
"type": "filter",
"expr": "isValid(datum.childInfo) && datum.childInfo.childCount > 0"
},
{
"type": "filter",
"expr": "configIncludeRoot ? true : isValid(datum.parentId)"
},
{"type": "formula", "expr": "toString(datum.id)", "as": "idStr"},
{"type": "project", "fields": ["idStr"]}
]
},
{
"name": "dataset-extent",
"source": "dataset-formatted-sorted",
"transform": [
{
"type": "aggregate",
"fields": ["startDate", "endDate"],
"ops": ["min", "max"],
"as": ["minStart", "maxEnd"]
}
]
},
{
"name": "children-by-parent",
"source": "dataset",
"transform": [
{
"type": "aggregate",
"groupby": ["parentId"],
"ops": ["values"],
"fields": ["id"],
"as": ["childIds"]
}
]
},
{
"name": "hierarchy-initial",
"source": "dataset-formatted-sorted",
"transform": [
{
"type": "filter",
"expr": "configIncludeRoot ? true : (isValid(datum['parentId']))"
},
{
"type": "formula",
"expr": "treeAncestors('dataset-formatted', datum.id)",
"as": "ancestors"
},
{
"type": "formula",
"expr": "length(datum.ancestors)-(configIncludeRoot ? 0 : 1)",
"as": "level"
},
{
"type": "formula",
"expr": "slice(pluck(datum.ancestors, 'id'), 1)",
"as": "ancestorIds"
},
{
"type": "lookup",
"from": "child-counts",
"key": "parentId",
"fields": ["id"],
"as": ["childCountData"]
},
{
"type": "formula",
"expr": "isValid(datum.childCountData) && datum.childCountData.childCount > 0",
"as": "hasChildren"
},
{
"type": "lookup",
"from": "children-by-parent",
"key": "parentId",
"fields": ["id"],
"values": ["childIds"],
"as": ["childrenData"]
},
{
"type": "formula",
"expr": "isValid(datum.childrenData) ? datum.childrenData.childIds : []",
"as": "immediateChildrenIds"
},
{
"type": "aggregate",
"ops": ["values"],
"fields": ["idObs"],
"groupby": [
"id",
"parentId",
"name",
"startDate",
"endDate",
"level",
"sort",
"hasChildren",
"ancestorIds"
],
"as": ["immediateChildrenIds"]
},
{
"type": "formula",
"expr": "pluck(slice(pluck(datum.immediateChildrenIds, 'immediateChildrenIds'), 1), 'id')",
"as": "immediateChildrenIds"
},
{
"type": "formula",
"expr": "datum.level === configInitialDepth ? datum.id : slice(datum.ancestorIds,-2)[configInitialDepth-1]",
"as": "firstAncestorId"
},
{
"type": "formula",
"expr": "!datum.hasChildren ? null : (datum.level < configInitialDepth ? 1 : (indexof(expandedNodeIdsCSV, ','+toString(datum.id)+',') >= 0 ? 1 : 0))",
"as": "isExpanded"
},
{"type": "formula", "expr": "interactionTypeHistory[0]", "as": "type"},
{
"type": "project",
"fields": [
"sort",
"id",
"parentId",
"name",
"startDate",
"endDate",
"level",
"ancestorIds",
"dependencyIds",
"hasChildren",
"firstAncestorId",
"immediateChildrenIds",
"isExpanded",
"type"
]
}
]
},
{
"name": "hierarchy-current-parent-collapsed-candidates",
"source": "hierarchy-initial",
"transform": [
{"type": "filter", "expr": "datum.isExpanded === 0"},
{"type": "filter", "expr": "datum.hasChildren"},
{"type": "project", "fields": ["id", "name"]}
]
},
{
"name": "hierarchy-current-parent-expanded-candidates",
"source": "hierarchy-initial",
"transform": [
{"type": "filter", "expr": "datum.isExpanded === 1"},
{"type": "filter", "expr": "datum.hasChildren"},
{"type": "project", "fields": ["id", "name"]}
]
},
{
"name": "hierarchy-visible",
"source": "hierarchy-initial",
"transform": [
{
"type": "lookup",
"from": "hierarchy-initial",
"key": "parentId",
"fields": ["id"],
"values": ["isExpanded"],
"as": ["parentIsExpanded"]
},
{
"type": "filter",
"expr": "!test('(\\\\b\\\\d+\\\\b).*\\\\b\\\\1\\\\b', (join(datum.ancestorIds, ',')+','+join(collapsedIds, ','))) || (datum.parentIsExpanded && indexof(expandedIds, datum.parentId) >= 0)"
},
{
"type": "formula",
"expr": "configInitialDepth >= datum.level || indexof(pluck(data('hierarchy-current-parent-expanded-candidates'), 'id'), datum.parentId) >= 0",
"as": "isVisible"
},
{"type": "filter", "expr": "datum.isVisible"},
{
"type": "window",
"ops": ["row_number"],
"as": ["index"],
"sort": {"field": ["sort"], "order": ["ascending"]}
},
{
"type": "joinaggregate",
"ops": ["min"],
"fields": ["index"],
"as": ["minIndex"]
},
{
"type": "formula",
"expr": "datum.index-datum.minIndex",
"as": "index"
},
{
"type": "formula",
"expr": "(datum.index)*configRow.rowHeight",
"as": "y1"
},
{
"type": "formula",
"expr": "isInitial && datum.level <= configInitialDepth ? 1 : null",
"as": "opacity"
},
{
"type": "project",
"fields": [
"index",
"sort",
"id",
"parentId",
"name",
"startDate",
"endDate",
"level",
"ancestorIds",
"dependencyIds",
"hasChildren",
"isVisible",
"firstAncestorId",
"immediateChildrenIds",
"isExpanded",
"y1",
"opacity",
"type"
]
},
{
"type": "filter",
"expr": "collapseAllClicked ? datum.level <= configInitialDepth : true"
}
]
},
{
"name": "hierarchy-last-visible-target",
"on": [
{
"trigger": "isValid(preLastClickedNodeTargets) && isValid(preLastClickedNodeTargets[0])",
"remove": false,
"insert": "{data: preLastClickedNodeTargets, timestamp: now(), source: 'hierarchy-visible-target'}"
},
{
"trigger": "isValid(initStamp) && timer==initStamp",
"remove": false,
"insert": "{data: data('hierarchy-initial'), timestamp: now(), source: 'hierarchy-initial'}"
}
],
"transform": [
{
"type": "formula",
"expr": "datum.timestamp || now()",
"as": "timestamp"
},
{
"type": "formula",
"expr": "datum.data || data('hierarchy-initial')",
"as": "data"
},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "timestamp", "order": "descending"},
"as": ["index"]
},
{
"type": "joinaggregate",
"ops": ["max"],
"fields": ["index"],
"as": ["count"]
},
{"type": "formula", "expr": "max(datum.count, 2)", "as": "count"},
{"type": "filter", "expr": "datum.index === 1"},
{"type": "formula", "expr": "datum.data[0].type", "as": "type"},
{"type": "flatten", "fields": ["data"]},
{"type": "formula", "expr": "datum.id || datum.data.id", "as": "id"},
{
"type": "formula",
"expr": "datum.parentId || datum.data.parentId",
"as": "parentId"
},
{
"type": "formula",
"expr": "datum.name || datum.data.name",
"as": "name"
},
{
"type": "formula",
"expr": "datum.sort || datum.data.sort",
"as": "sort"
},
{
"type": "formula",
"expr": "datum.ancestorIds || datum.data.ancestorIds",
"as": "ancestorIds"
},
{
"type": "formula",
"expr": "datum.firstAncestorId || datum.data.firstAncestorId",
"as": "firstAncestorId"
},
{
"type": "formula",
"expr": "datum.startDate || datum.data.startDate",
"as": "startDate"
},
{
"type": "formula",
"expr": "datum.endDate || datum.data.endDate",
"as": "endDate"
},
{
"type": "formula",
"expr": "datum.hasChildren || datum.data.hasChildren",
"as": "hasChildren"
},
{
"type": "formula",
"expr": "datum.level || datum.data.level",
"as": "level"
},
{
"type": "formula",
"expr": "datum.immediateChildrenIds || datum.data.immediateChildrenIds",
"as": "immediateChildrenIds"
},
{
"type": "formula",
"expr": "datum.isExpanded || datum.data.isExpanded",
"as": "isExpanded"
},
{
"type": "formula",
"expr": "datum.isVisible || datum.data.isVisible || (datum.source === 'hierarchy-initial' && (configInitialDepth >= datum.level)) || false",
"as": "isVisible"
},
{"type": "formula", "expr": "datum.isVisible ? 1 : 0", "as": "opacity"},
{
"type": "formula",
"expr": "datum.level <= configInitialDepth",
"as": "visibleAtInitialization"
},
{
"type": "window",
"ops": ["row_number"],
"groupby": ["visibleAtInitialization"],
"as": ["initialIndex"],
"sort": {"field": ["sort"], "order": ["ascending"]}
},
{
"type": "formula",
"expr": "(datum.initialIndex-1) * configRow.rowHeight",
"as": "yInitial"
},
{
"type": "formula",
"expr": "datum.visibleAtInitialization ? datum.yInitial : 0",
"as": "yInitial"
},
{
"type": "formula",
"expr": "datum.data.y1 || datum.yInitial",
"as": "y1"
},
{
"type": "window",
"ops": ["max"],
"fields": ["y1"],
"frame": [null, 0],
"sort": {"field": "sort"},
"as": ["y1"]
},
{
"type": "formula",
"expr": "datum.animateCount || datum.data.animateCount",
"as": "animateCount"
},
{
"type": "project",
"fields": [
"animateCount",
"timestamp",
"id",
"parentId",
"name",
"sort",
"ancestorIds",
"firstAncestorId",
"startDate",
"endDate",
"hasChildren",
"level",
"immediateChildrenIds",
"isVisible",
"isExpanded",
"source",
"y1",
"opacity",
"type"
]
}
]
},
{
"name": "hierarchy-visible-source",
"on": [
{
"trigger": "data('hierarchy-last-visible-target')",
"remove": true,
"insert": "data('hierarchy-last-visible-target')"
}
],
"transform": []
},
{
"name": "height",
"source": "hierarchy-visible",
"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": "animStartTick",
"insert": "{height: data('height')[0].height, timestamp: now()}",
"remove": false
}
],
"transform": [
{
"type": "formula",
"expr": "datum.height || data('height')[0].height",
"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 + (interactionTypeHistory[0] === 'sort' ? configAnimationDuration.sort : 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": "hierarchy-visible-target",
"source": "hierarchy-initial",
"transform": [
{
"type": "lookup",
"from": "hierarchy-visible",
"key": "id",
"fields": ["id"],
"as": ["visibleValues"]
},
{
"type": "formula",
"expr": "isValid(datum.visibleValues)",
"as": "isVisible"
},
{
"type": "window",
"ops": ["dense_rank"],
"groupby": ["isVisible"],
"as": ["index"],
"sort": {"field": ["sort"], "order": ["ascending"]}
},
{
"type": "formula",
"expr": "!datum.isVisible ? null : datum.visibleValues.y1",
"as": "y1"
},
{
"type": "window",
"ops": ["max"],
"fields": ["y1"],
"frame": [null, 0],
"sort": {"field": "sort"},
"as": ["y1"]
},
{
"type": "formula",
"expr": "(datum.level <= configInitialDepth || datum.isVisible) ? 1 : 0",
"as": "opacity"
},
{
"type": "window",
"ops": ["dense_rank"],
"as": ["index"],
"sort": {"field": ["sort"], "order": ["ascending"]}
},
{"type": "formula", "expr": "animateCount", "as": "animateCount"},
{
"type": "project",
"fields": [
"animateCount",
"index",
"id",
"parentId",
"name",
"sort",
"isVisible",
"firstAncestorId",
"collapseAllY1",
"startDate",
"endDate",
"hasChildren",
"level",
"immediateChildrenIds",
"ancestorIds",
"isExpanded",
"opacity",
"y1",
"type"
]
},
{"type": "collect", "sort": {"field": "index"}}
]
},
{
"name": "hierarchy-pre-animation",
"on": [
{
"trigger": "animationPulse",
"insert": "data('hierarchy-initial')",
"remove": true
},
{
"trigger": "isValid(initStamp) && timer==initStamp",
"remove": true,
"insert": "data('hierarchy-initial')"
}
],
"transform": [
{
"type": "lookup",
"key": "id",
"fields": ["id"],
"from": "hierarchy-visible-source",
"as": ["sourceValues"]
},
{
"type": "lookup",
"key": "id",
"fields": ["id"],
"from": "hierarchy-visible-target",
"as": ["targetValues"]
},
{
"type": "filter",
"expr": "isValid(datum.sourceValues) || isValid(datum.targetValues)"
},
{
"type": "formula",
"as": "sourceOpacity",
"expr": "isValid(datum.sourceValues) ? datum.sourceValues.opacity : 0"
},
{
"type": "formula",
"as": "targetOpacity",
"expr": "isValid(datum.targetValues) ? datum.targetValues.opacity : 0"
},
{
"type": "formula",
"as": "sourceY1",
"expr": "isValid(datum.sourceValues) ? datum.sourceValues.y1 : (isValid(datum.targetValues) ? datum.targetValues.collapseAllY1 : null)"
},
{
"type": "formula",
"as": "targetY1",
"expr": "isValid(datum.targetValues) ? datum.targetValues.y1 : (isValid(datum.sourceValues) ? datum.sourceValues.collapseAllY1 : null)"
},
{
"type": "filter",
"expr": "isValid(datum.sourceY1) && isValid(datum.targetY1)"
},
{
"type": "formula",
"expr": "datum.targetValues.isExpanded",
"as": "isExpanded"
},
{
"type": "lookup",
"from": "dataset-formatted",
"key": "id",
"fields": ["id"],
"values": [
"decimalPercentComplete",
"dependencyId",
"color",
"dependencies"
]
},
{"type": "collect", "sort": {"field": "sort"}}
]
},
{
"name": "hierarchy-animation-bounds",
"values": [{"start": 0, "end": -1}],
"on": [
{
"trigger": "animStartTick",
"insert": "{start: animStartTick, end: animStartTick+(interactionTypeHistory[0] === 'sort' ? configAnimationDuration.sort : configAnimationDuration.nodeExpandCollapse)}",
"remove": true
}
],
"transform": []
},
{
"name": "hierarchy-animation",
"on": [
{
"trigger": "isValid(data('hierarchy-pre-animation')[0])",
"remove": true,
"insert": "isValid(data('hierarchy-pre-animation')[0]) ? data('hierarchy-pre-animation') : []"
}
],
"transform": [
{
"type": "lookup",
"from": "dataset-formatted",
"key": "id",
"fields": ["id"],
"values": [
"decimalPercentComplete",
"dependencyId",
"color",
"dependencies",
"label"
]
},
{
"type": "formula",
"expr": "scale('xScaleGanttTimeSeries', datum.startDate)",
"as": "x1"
},
{
"type": "formula",
"expr": "scale('xScaleGanttTimeSeries', datum.endDate)",
"as": "x2"
},
{
"type": "formula",
"expr": "scale('xScaleGanttTimeSeries', span([datum.startDate, datum.endDate])*datum.decimalPercentComplete+datum.startDate)",
"as": "x2Progress"
},
{
"type": "formula",
"expr": "lerp([datum.sourceY1, datum.targetY1], rowAnimationTEased)",
"as": "y1"
},
{"type": "formula", "expr": "datum.y1+configRow.rowHeight", "as": "y2"},
{"type": "formula", "expr": "scrollY+datum.y2", "as": "bufferHeight"},
{
"type": "filter",
"expr": "datum.bufferHeight <= (adjustedHeight+configRow.rowHeight) && (datum.bufferHeight) >= 0"
},
{
"type": "formula",
"expr": "datum.y1+configRow.rowHeight*(datum.hasChildren ? 0.225 : 0.325)",
"as": "y1WithPadding"
},
{
"type": "formula",
"expr": "datum.y2-configRow.rowHeight*(datum.hasChildren ? 0.225 : 0.325)",
"as": "y2WithPadding"
},
{
"type": "formula",
"expr": "datum.y2WithPadding-(configRow.rowHeight*0.225)*rowAnimationTEased",
"as": "y2WithPaddingParentExpanding"
},
{
"type": "formula",
"expr": "datum.y2WithPadding-(configRow.rowHeight*0.225)",
"as": "y2WithPaddingParentExpanded"
},
{
"type": "formula",
"expr": "datum.y2WithPadding-(configRow.rowHeight*0.225)*(1-rowAnimationTEased)",
"as": "y2WithPaddingParentCollapsing"
},
{
"type": "formula",
"expr": "datum.y2WithPadding-(configRow.rowHeight*0.225)",
"as": "y2WithPaddingParentCollapsed"
},
{
"type": "formula",
"expr": "!isValid(datum.isExpanded) || !isValid(datum.sourceValues.isExpanded) ? datum.y2WithPadding : datum.isExpanded === 1 ? datum.sourceValues.isExpanded === 0 ? datum.y2WithPaddingParentExpanding : datum.y2WithPaddingParentExpanded : datum.sourceValues.isExpanded === 1 ? datum.isExpanded === 0 ? datum.y2WithPaddingParentCollapsing : datum.y2WithPaddingParentCollapsed : datum.y2WithPadding",
"as": "y2WithPaddingParent"
},
{
"type": "formula",
"expr": "lerp([datum.sourceOpacity, datum.targetOpacity], rowAnimationTEased)*0.35",
"as": "opacity"
},
{
"type": "formula",
"expr": "lerp([datum.sourceOpacity, datum.targetOpacity], rowAnimationTEased)",
"as": "fullOpacity"
},
{"type": "filter", "expr": "datum.opacity>0"},
{
"type": "window",
"ops": ["row_number"],
"sort": {"field": "sort"},
"as": ["index"]
},
{"type": "collect", "sort": {"field": "sort"}}
]
},
{
"name": "dependency-links",
"source": "hierarchy-animation",
"transform": [
{"type": "filter", "expr": "showDependencies"},
{
"type": "filter",
"expr": "datum.fullOpacity > 0 && isValid(datum.dependencies) && length(datum.dependencies) > 0"
},
{"type": "formula", "expr": "datum.dependencies", "as": "dependencyId"},
{"type": "flatten", "fields": ["dependencyId"]},
{"type": "formula", "expr": "datum.id", "as": "targetId"},
{"type": "formula", "expr": "+datum.dependencyId", "as": "sourceId"},
{
"type": "filter",
"expr": "isValid(datum.sourceId) && isValid(datum.targetId)"
},
{
"type": "lookup",
"from": "hierarchy-visible-target",
"key": "id",
"fields": ["sourceId"],
"values": ["endDate", "index"],
"as": ["sourceEndDate", "sourceRowNumber"]
},
{
"type": "lookup",
"from": "hierarchy-visible-target",
"key": "id",
"fields": ["targetId"],
"values": ["startDate", "endDate", "index"],
"as": ["targetStartDate", "targetEndDate", "targetRowNumber"]
},
{
"type": "lookup",
"from": "hierarchy-animation",
"key": "id",
"fields": ["sourceId"],
"values": ["y1"],
"as": ["sourceY1Animated"]
},
{
"type": "filter",
"expr": "isValid(datum.sourceEndDate) && isValid(datum.sourceRowNumber) && isValid(datum.targetStartDate) && isValid(datum.targetEndDate) && isValid(datum.targetRowNumber)"
},
{
"type": "filter",
"expr": "isValid(datum.y1) && isValid(datum.sourceY1Animated)"
},
{
"type": "formula",
"expr": "datum.sourceY1Animated+configRow.rowHeight/2",
"as": "sourceY"
},
{
"type": "formula",
"expr": "datum.y1+configRow.rowHeight/2",
"as": "targetY"
},
{"type": "formula", "expr": "datum.opacity", "as": "opacity"},
{
"type": "formula",
"expr": "+format(scale('xScaleGanttTimeSeries', datum.sourceEndDate), '.4f')+5",
"as": "sourceX"
},
{
"type": "formula",
"expr": "+format(scale('xScaleGanttTimeSeries', datum.targetStartDate), '.4f')-2.5",
"as": "targetX"
},
{
"type": "formula",
"expr": "{x: datum.sourceX, y: datum.sourceY}",
"as": "source"
},
{
"type": "formula",
"expr": "{x: datum.targetX, y: datum.targetY}",
"as": "target"
},
{
"type": "formula",
"expr": "{startDate: datum.targetStartDate, endDate: datum.targetEndDate}",
"as": "targetTargetValues"
},
{"type": "fold", "fields": ["source", "target"], "as": ["key", "xy"]},
{
"type": "formula",
"expr": "datum.sourceId+'-'+datum.targetId",
"as": "sourceTargetCompId"
},
{
"type": "window",
"ops": ["dense_rank"],
"sort": {"field": "sourceTargetCompId"},
"as": ["linkId"]
},
{
"type": "formula",
"expr": "(datum.targetY-datum.sourceY)/2",
"as": "halfHeight"
},
{
"type": "formula",
"expr": "datum.targetRowNumber-datum.sourceRowNumber",
"as": "rowSeparationCount"
},
{"type": "formula", "expr": "[1,2,3]", "as": "sort"},
{"type": "flatten", "fields": ["sort"]},
{
"type": "formula",
"expr": "datum.sort+(datum.key === 'source' ? 0 : 3)",
"as": "sort"
},
{
"type": "formula",
"expr": "datum.sort === 2 ? {x: datum.xy.x+15, y:datum.xy.y} : datum.xy",
"as": "xy"
},
{
"type": "formula",
"expr": "datum.sort === 3 ? {x:datum.xy.x+15, y:datum.xy.y+datum.halfHeight} : datum.xy",
"as": "xy"
},
{
"type": "formula",
"expr": "datum.sort === 4 ? {x:datum.xy.x-15, y:datum.xy.y-datum.halfHeight} : datum.xy",
"as": "xy"
},
{
"type": "formula",
"expr": "datum.sort === 5 ? {x:datum.xy.x-15, y:datum.xy.y} : datum.xy",
"as": "xy"
},
{
"type": "filter",
"expr": "inrange(invert('xScaleGanttTimeSeries', datum.xy.x), xDomain)"
},
{"type": "filter", "expr": "datum.xy.x <= ganttDimensions.width"},
{
"type": "joinaggregate",
"ops": ["count"],
"fields": ["linkId"],
"groupby": ["linkId"],
"as": ["segmentCount"]
},
{"type": "filter", "expr": "datum.segmentCount === 6"}
]
},
{
"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"
}
]
},
{
"transform": [
{"type": "formula", "expr": "datum.name || null", "as": "name"},
{"type": "formula", "expr": "datum.field || null", "as": "field"},
{"type": "formula", "expr": "datum.type || 'text'", "as": "type"},
{"type": "formula", "expr": "datum.format || null", "as": "format"},
{"type": "formula", "expr": "datum.label || null", "as": "label"},
{"type": "formula", "expr": "datum.align || 'left'", "as": "align"},
{
"type": "formula",
"expr": "datum.allowableWidth || 0",
"as": "allowableWidth"
},
{
"type": "formula",
"expr": "datum.alwaysShow || false",
"as": "alwaysShow"
},
{
"type": "formula",
"expr": "datum.boldValue || false",
"as": "boldValue"
},
{"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.allowableWidth)+(configColumn.innerPadding*(datum.index-1))",
"as": "x"
},
{
"type": "window",
"ops": ["lead"],
"fields": ["x"],
"sort": {"field": "index"},
"frame": [0, null],
"as": ["x2"]
},
{
"type": "formula",
"expr": "datum.x2 || (datum.x+datum.allowableWidth)",
"as": "x2"
},
{
"type": "joinaggregate",
"ops": ["max"],
"fields": ["alwaysShow"],
"as": ["anyVisibleColumns"]
},
{
"type": "formula",
"expr": "showDetails ? 'show' : (datum.alwaysShow ? 'show' : 'hide')",
"as": "show"
},
{
"type": "window",
"ops": ["max"],
"fields": ["show"],
"frame": [0, null],
"as": ["show"]
},
{
"type": "formula",
"expr": "datum.show === 'show' ? datum.x : 0",
"as": "showX"
},
{
"type": "formula",
"expr": "datum.show === 'show' ? datum.x2-datum.x : 0",
"as": "showAllowableWidth"
},
{
"type": "window",
"ops": ["lead"],
"fields": ["showAllowableWidth"],
"sort": {"field": "index"},
"frame": [0, null],
"as": ["lastShownRow"]
},
{
"type": "formula",
"expr": "!isValid(datum.lastShownRow) || datum.lastShownRow === 0",
"as": "lastShownRow"
},
{
"type": "formula",
"expr": "datum.show === 'show' && datum.lastShownRow ? datum.x2-datum.x : 0",
"as": "showLastAllowableWidth"
},
{
"type": "window",
"ops": ["last_value", "max", "max"],
"fields": ["allowableWidth", "showX", "showLastAllowableWidth"],
"sort": {"field": "index"},
"frame": [null, null],
"as": ["lastAllowableWidth", "lastX", "showLastAllowableWidth"]
},
{
"type": "formula",
"expr": "(showDetails ? datum.lastAllowableWidth : datum.showLastAllowableWidth)+datum.lastX",
"as": "totalWidth"
},
{
"type": "formula",
"expr": "!showDetails && !datum.anyVisibleColumns ? 0 : datum.totalWidth + configRow.levelIndentWidth",
"as": "totalWidth"
}
],
"name": "column-data",
"values": [
{
"name": "name",
"field": "name",
"type": "text",
"format": null,
"label": "Task",
"align": "left",
"allowableWidth": 95,
"alwaysShow": true,
"boldValue": true
},
{
"name": "startDate",
"field": "startDate",
"type": "date",
"format": "%m/%d/%Y",
"label": "Start Date",
"align": "right",
"allowableWidth": 55,
"alwaysShow": false,
"boldValue": false
},
{
"name": "endDate",
"field": "endDate",
"type": "date",
"format": "%m/%d/%Y",
"label": "End Date",
"align": "right",
"allowableWidth": 55,
"alwaysShow": false,
"boldValue": false
},
{
"name": "duration",
"field": "duration",
"type": "text",
"format": null,
"label": "Duration",
"align": "right",
"allowableWidth": 50,
"alwaysShow": false,
"boldValue": false
},
{
"name": "progress",
"field": "decimalPercentComplete",
"type": "percentage",
"format": ".0%",
"label": "Progress",
"align": "center",
"allowableWidth": 75,
"alwaysShow": false,
"boldValue": false
}
]
},
{
"transform": [
{"type": "filter", "expr": "isValid(mouseoverNodeDatum)"},
{"type": "formula", "expr": "data('dataset')", "as": "dataset"},
{"type": "flatten", "fields": ["dataset"]},
{
"type": "filter",
"expr": "mouseoverNodeDatum.id === datum.dataset.id"
},
{"type": "window", "ops": ["row_number"], "as": ["index"]},
{
"type": "formula",
"expr": "mouseoverNodeDatum[datum.field] || datum.dataset[datum.field]",
"as": "value"
},
{
"type": "formula",
"expr": "isValid(datum.format) && (datum.type === 'date' || datum.type === 'text') ? datum.type === 'date' ? timeFormat(datum.value, datum.format) : format(datum.value, datum.format) : datum.value",
"as": "value"
},
{
"type": "formula",
"expr": "pad('', datum.index, '‎')+datum.label",
"as": "label"
},
{
"type": "pivot",
"field": {"field": "label"},
"value": "value",
"op": "min"
}
],
"name": "gantt-item-tooltip-data",
"values": [
{"field": "name", "label": "Task", "type": "text", "format": null},
{
"field": "startDate",
"label": "Start Date",
"type": "date",
"format": "%m/%d/%Y"
},
{
"field": "endDate",
"label": "End Date",
"type": "date",
"format": "%m/%d/%Y"
},
{
"field": "duration",
"label": "Duration",
"type": "text",
"format": null
}
]
}
],
"config": {"text": {"font": "Segoe UI"}}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment