Skip to content

Instantly share code, notes, and snippets.

@Giammaria
Last active March 3, 2025 15:30
Show Gist options
  • Save Giammaria/bf2d8a0ab4caf6086173d61cdb05a382 to your computer and use it in GitHub Desktop.
Save Giammaria/bf2d8a0ab4caf6086173d61cdb05a382 to your computer and use it in GitHub Desktop.
20250218_basic_zoom_and_pan_v1_v
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"signals": [
{"name": "adjustedHeight", "value": 300},
{"name": "width", "value": 1200},
{
"name": "columnsWidthPercent",
"value": 0.25,
"update": "clamp(columnsWidthPercent, 0, 0.5)"
},
{
"name": "offsetUnit",
"value": "year",
"update": "indexof(['hours', 'day', 'month', 'year'], offsetUnit) < 0 ? 'month' : offsetUnit"
},
{
"name": "negativeDateOffsetFromToday",
"value": -5,
"update": "clamp(negativeDateOffsetFromToday, -100, 0)"
},
{
"name": "positiveOffsetFromToday",
"value": 5,
"update": "clamp(positiveOffsetFromToday, 1, 100)"
},
{
"name": "recordCount",
"value": 40,
"update": "clamp(recordCount, 1, 1000)"
},
{
"name": "blockHeight",
"value": 15,
"update": "clamp(blockHeight, 10, 50)"
},
{
"name": "configVerticalScrollbar",
"description": "configurations for the vertical scroll bar",
"update": "{enabled: actualHeight>adjustedHeight ,innerPadding: 10, track: {width: 10, height: extent([actualHeight, adjustedHeight])[0], fill: '#F3F3F3'}, handle: {height: max((adjustedHeight/actualHeight)*extent([actualHeight, adjustedHeight])[0], 30), fill: '#ddd', hover: {fill: '#888'}}}"
},
{
"name": "verticalScrollbarMouseDown",
"description": "boolean indicating whether the vertical scrollbar is currently being clicked",
"value": false,
"on": [
{
"events": "@group_verticalScrollbar:pointerdown",
"update": "!panAndZoomMode && configVerticalScrollbar.enabled"
},
{
"events": {
"type": "mouseout",
"scope": "view",
"markname": "group_verticalScrollbar",
"filter": ["!event.pointerdown"]
},
"update": "false"
},
{
"events": {
"type": "mouseover",
"scope": "scope",
"markname": "group_verticalScrollbar",
"filter": ["event.pointerdown"]
},
"update": "!panAndZoomMode"
},
{"events": {"type": "pointerup"}, "update": "false"}
]
},
{
"name": "verticalScrollIncrement",
"update": "0.01 * (actualHeight-adjustedHeight)/actualHeight"
},
{
"name": "____debugScaleDomain",
"value": null,
"update": "domain('scaleScrollHandleY')"
},
{
"name": "____debugScaleRange",
"value": null,
"update": "range('scaleScrollHandleY')"
},
{
"name": "____debugInvertY",
"value": null,
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background"
},
"update": "invert('scaleScrollHandleY', clamp(y(group())+300, range('scaleScrollHandleY')[0], range('scaleScrollHandleY')[1]))"
}
]
},
{
"name": "yPanStart",
"value": null,
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background"
},
"update": "clamp(invert('scaleScrollHandleY', y(group()) + range('scaleScrollHandleY')[0]), 0, (actualHeight-adjustedHeight)/actualHeight)"
}
]
},
{
"name": "yPanPrevious",
"value": null,
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background"
},
"update": "verticalScrollPercentage"
}
]
},
{
"name": "verticalScrollVelocity",
"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": "verticalScrollPercentage",
"description": "the percentage of the current vertical scroll",
"value": 0,
"on": [
{
"events": {
"type": "wheel",
"consume": true,
"force": true,
"source": "view",
"filter": ["!event.ctrlKey", "!event.shiftKey"]
},
"update": "panAndZoomMode ? verticalScrollPercentage : clamp(verticalScrollPercentage - (-event.deltaY * pow(4, event.deltaMode) * 0.002 * adjustedHeight / actualHeight), 0, (actualHeight-adjustedHeight)/actualHeight)"
},
{
"events": "window:keydown[event.key === 'ArrowUp' || event.key === 'ArrowDown']",
"update": "clamp(verticalScrollPercentage + verticalScrollIncrement * (event.key === 'ArrowDown' ? 1 : -1), 0, (actualHeight-adjustedHeight)/actualHeight)"
},
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "rect-gantt-background",
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "panAndZoomMode && isValid(yPanStart) && isValid(y(group())) ? clamp(verticalScrollPercentage + (yPanPrevious + (invert('scaleScrollHandleY', y(group()) + range('scaleScrollHandleY')[0]) - yPanStart) - verticalScrollPercentage) * 0.35, 0, (actualHeight - adjustedHeight) / actualHeight) : verticalScrollPercentage"
},
{
"events": "@group_verticalScrollbar:pointerdown",
"update": "panAndZoomMode ? verticalScrollPercentage : !configVerticalScrollbar.enabled ? 0 : clamp(verticalScrollPercentage - (-event.deltaY * pow(4, event.deltaMode) * 0.001), 0, (actualHeight-adjustedHeight)/actualHeight)"
},
{
"events": {
"type": "pointermove",
"source": "scope",
"markname": "group_verticalScrollbar",
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "panAndZoomMode ? verticalScrollPercentage : !configVerticalScrollbar.enabled ? 0 : invert('scaleScrollHandleY', y(group()))"
},
{
"events": {
"type": "pointermove",
"source": "scope",
"markname": "group_verticalScrollbar",
"between": [{"type": "pointerdown"}, {"type": "mouseout"}]
},
"update": "panAndZoomMode ? verticalScrollPercentage : !configVerticalScrollbar.enabled ? 0 : isValid(group()) ? invert('scaleScrollHandleY', y(group())) : verticalScrollPercentage"
},
{
"events": {"signal": "!configVerticalScrollbar.enabled"},
"update": "0"
}
]
},
{
"name": "verticalScrollbarMouseOver",
"description": "boolean indicating whether the vertical scrollbar is currently being moused over",
"value": false,
"on": [
{
"events": "@group_verticalScrollbar:mouseover",
"update": "!panAndZoomMode && configVerticalScrollbar.enabled"
},
{"events": "@group_verticalScrollbar:mouseout", "update": "false"}
]
},
{"name": "columnsWidth", "update": "width*columnsWidthPercent"},
{
"name": "ganttWidth",
"update": "width*(1-columnsWidthPercent)-(actualHeight>adjustedHeight ? configVerticalScrollbar.track.width*1.5 : 0)"
},
{
"name": "configTogglepanAndZoomMode",
"description": "configurations for the mouse wheel granularity control",
"update": "{enabled: true, initialValue: false, xOffset: 0, track: {height: 7.5, width: 25, cornerRadius: 5, fill: '#d1e0ec', stroke: '#777', strokeWidth: 1}, handle: {stroke: '#777', strokeWidth: 1, fill: '#fff'}, label: {text: 'Mouse wheel granularity', font: 'Segoe UI', fontSize: 11, fill: '#888', fontStyle: 'regular', dx: 5}, on: {fill: '#d1e0ec', fillOpacity: 1, stroke: '#777', strokeWidth: 1, fontWeight: 700}, tooltip: {object: {title: 'Mouse Wheel Scroll vs. Zoom', '‎‎':'‎', '• ‎ ‎ ‎ ‎ ‎ ‎': 'Click here to toggle between vertical scrolling and date granularity zooming.', '‎‎‎':'‎', '• ‎ ‎ ‎ ‎ ‎ ‎‎': 'Alternatively, press the Ctrl key to quickly toggle between modes.'}}}"
},
{
"name": "configDateStepSlider",
"description": "configurations for the date granularity slider control",
"update": "{enabled: true, innerPadding: 10, 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": "dateGranularitySliderMouseDown",
"description": "boolean indicating whether the date granularity slider is currently being clicked",
"value": false,
"on": [
{
"events": "@dateGranularitySliderInteractive_rect:pointerdown",
"update": "true"
},
{"events": "pointerup", "update": "false"}
]
},
{
"name": "todayDate",
"update": "utc(year(now()),month(now()),date(now()))"
},
{
"name": "dynamicX",
"update": "clamp(2 - (abs(span(xDomainInitial) - 180000000) / 90000000), 0.5, 2)"
},
{
"name": "scrollDomain",
"update": "[xDomainInitial[0] - span(xDomainInitial) * dynamicX, xDomainInitial[1] + span(xDomainInitial) * dynamicX]"
},
{
"name": "horizontalPercentageBounds",
"init": "{min: span(xDomain)/span(domain('xScaleHorizontalScrollMap'))/2, max: 1-span(xDomain)/span(domain('xScaleHorizontalScrollMap'))/2, spanPercentage: span(xDomain)/span(domain('xScaleHorizontalScrollMap'))}",
"on": [
{
"events": {"signal": "xDomain"},
"update": "{min: span(xDomain)/span(domain('xScaleHorizontalScrollMap'))/2, max: 1-span(xDomain)/span(domain('xScaleHorizontalScrollMap'))/2, spanPercentage: span(xDomain)/span(domain('xScaleHorizontalScrollMap'))}"
}
]
},
{
"name": "xPanStart",
"value": null,
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background"
},
"update": "panAndZoomMode ? invert('x', x() - columnsWidth) : xPanStart"
}
]
},
{
"name": "panSensitivity",
"value": 1,
"update": "clamp(panSensitivity, 1, 10)"
},
{
"name": "xPanPrevious",
"value": null,
"on": [
{
"events": {
"type": "pointerdown",
"source": "scope",
"markname": "rect-gantt-background"
},
"update": "horizontalScrollPercentage.current"
}
]
},
{
"name": "horizontalScrollIncrement",
"update": "horizontalPercentageBounds.spanPercentage / 20 * (0.2 + 0.8 * (1 - pow(abs((horizontalScrollPercentage.current - (horizontalPercentageBounds.min + horizontalPercentageBounds.max) / 2)) / ((horizontalPercentageBounds.max - horizontalPercentageBounds.min) / 2), 2)))"
},
{
"name": "horizontalScrollVelocity",
"value": 0,
"on": [
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "rect-gantt-background",
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "!panAndZoomMode ? horizontalScrollVelocity : (clamp(xPanPrevious + ((invert('xScaleHorizontalScrollMap', x()-columnsWidth) - xPanStart) / span(scrollDomain) * (span(xDomainPreliminary) / span(xDomainInitial))), horizontalPercentageBounds.min, horizontalPercentageBounds.max) - horizontalScrollPercentage.current) * (0.1 * pow(span(domain('xScaleHorizontalScrollMap')) / span(xDomain), 0.5)) + ((horizontalScrollVelocity > 0 && (invert('xScaleHorizontalScrollMap', x()-columnsWidth) - xPanStart) < 0) || (horizontalScrollVelocity < 0 && (invert('xScaleHorizontalScrollMap', x()-columnsWidth) - xPanStart) > 0) ? horizontalScrollVelocity * 0.25 : horizontalScrollVelocity * 0.8)"
}
]
},
{
"name": "horizontalScrollPercentage",
"init": "{current: 0.5, previous: 0.5, debug: 'start', delta: 0}",
"on": [
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "rect-gantt-background",
"throttle": 40,
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "!panAndZoomMode ? horizontalScrollPercentage : {current: clamp(horizontalScrollPercentage.current + horizontalScrollVelocity * 0.5 * (span(xDomainPreliminary) / span(xDomainInitial)), horizontalPercentageBounds.min, horizontalPercentageBounds.max), previous: horizontalScrollPercentage.current, delta: horizontalScrollVelocity > 0 ? 1 : (horizontalScrollVelocity < 0 ? -1 : 0), type: 'velocity'}"
},
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "gantt-horizontal-scroll-map-interactive-rect",
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "{current: clamp((invert('xScaleHorizontalScrollMap', x(group()-60)-columnsWidth) - scrollDomain[0]) / span(scrollDomain), horizontalPercentageBounds.min, horizontalPercentageBounds.max), previous: horizontalScrollPercentage.current, delta: (invert('xScaleHorizontalScrollMap', x(group())-columnsWidth) - scrollDomain[0]) > (horizontalScrollPercentage.current * span(scrollDomain) + scrollDomain[0]) ? 1 : -1, directionChange: ((invert('xScaleHorizontalScrollMap', x(group())-columnsWidth) - scrollDomain[0]) > (horizontalScrollPercentage.current * span(scrollDomain) + scrollDomain[0]) ? 1 : -1) !== horizontalScrollPercentage.delta, type: 'pointer'}"
},
{
"events": {
"type": "pointerdown",
"source": "scope",
"consume": true,
"markname": "gantt-horizontal-scroll-map-interactive-rect"
},
"update": "{current: clamp((invert('xScaleHorizontalScrollMap', x(group()-60)-columnsWidth) - scrollDomain[0]) / span(scrollDomain), horizontalPercentageBounds.min, horizontalPercentageBounds.max), previous: horizontalScrollPercentage.current, delta: (invert('xScaleHorizontalScrollMap', x(group())-columnsWidth) - scrollDomain[0]) > (horizontalScrollPercentage.current * span(scrollDomain) + scrollDomain[0]) ? 1 : -1, directionChange: ((invert('xScaleHorizontalScrollMap', x(group())-columnsWidth) - scrollDomain[0]) > (horizontalScrollPercentage.current * span(scrollDomain) + scrollDomain[0]) ? 1 : -1) !== horizontalScrollPercentage.delta, type: 'pointer'}"
},
{
"events": {"type": "pointerup"},
"update": "horizontalScrollPercentage"
},
{
"events": "wheel![event.shiftKey]",
"force": true,
"update": "{current: clamp(horizontalScrollPercentage.current + horizontalScrollIncrement * (event.deltaY < 0 ? -1 : 1), horizontalPercentageBounds.min, horizontalPercentageBounds.max), previous: horizontalScrollPercentage.current, delta: event.deltaY !== 0 ? (event.deltaY < 0 ? -1 : 1) : horizontalScrollPercentage.delta, directionChange: (event.deltaY !== 0 ? (event.deltaY < 0 ? -1 : 1) : horizontalScrollPercentage.delta) !== horizontalScrollPercentage.delta, type: 'mousewheel'}"
},
{
"events": "window:keydown[event.key === 'ArrowLeft' || event.key === 'ArrowRight']",
"update": "{current: clamp(horizontalScrollPercentage.current + horizontalScrollIncrement * (event.key === 'ArrowLeft' ? -1 : 1), horizontalPercentageBounds.min, horizontalPercentageBounds.max), previous: horizontalScrollPercentage.current, delta: event.key === 'ArrowLeft' ? -1 : 1, type: 'arrowkey'}"
},
{
"events": {"signal": "resetAnimationBounds"},
"update": "!isValid(resetAnimationBounds) || !isValid(horizontalScrollPercentage.delta) ? horizontalScrollPercentage : horizontalScrollPercentage.delta === -1 ? {current: 0.49, previous: 0.51} : {current: 0.51, previous: 0.49}"
}
]
},
{
"name": "prevDeltaY",
"value": 0,
"on": [
{
"events": "wheel![event.shiftKey]",
"force": true,
"update": "event.deltaY"
}
]
},
{"name": "minDateBandwidth", "value": 20},
{
"name": "panAndZoomMode",
"description": "the currently toggled value for the mouse wheel granularity control",
"init": "configTogglepanAndZoomMode.initialValue",
"on": [
{
"events": "@mouseWheel-granularity-interactive-rect:pointerdown",
"update": "!panAndZoomMode"
},
{
"events": "window:keydown[event.ctrlKey]{0, 100}",
"update": "panAndZoomMode ? false : true"
}
]
},
{
"name": "wheelDelta",
"value": 0,
"on": [
{
"events": "wheel!",
"force": true,
"update": "!isValid(resetAnimationBounds) && panAndZoomMode && x()> columnsWidth ? pow(1.001 + 0.0005 * log(1 + span(scrollDomain) / span(xDomain)), (event.deltaY) * pow(8, event.deltaMode)) : 0"
}
]
},
{
"name": "zoom",
"value": 1,
"on": [
{
"events": "wheel![!event.shiftKey]",
"force": true,
"update": "!panAndZoomMode || wheelDelta === 0 ? zoom : wheelDelta"
}
]
},
{"name": "xDomainInitial", "update": "data('xDomainInitial')[0]['domain']"},
{
"name": "initialDomainCenter",
"update": "xDomainInitial[0] + span(xDomainInitial) / 2"
},
{
"name": "currentDomainCenter",
"init": "initialDomainCenter",
"on": [
{
"events": {"signal": "xDomain"},
"update": "xDomain[0] + span(xDomain) / 2"
}
]
},
{
"name": "xDomainBounds",
"update": "{min: 180000000, max: round((width/0.075)* scale('dateUnitIncrementMS', 'day'))}"
},
{
"name": "xDomain",
"init": "xDomainInitial",
"on": [
{
"events": {"signal": "xDomainPreliminary"},
"update": "span(xDomainPreliminary)<xDomainBounds.min ? [currentDomainCenter - xDomainBounds.min/2, currentDomainCenter + xDomainBounds.min/2] : [max(xDomainPreliminary[0], scrollDomain[0]), min(xDomainPreliminary[1], scrollDomain[1])]"
}
]
},
{
"name": "granularityPercentage",
"on": [
{
"events": {
"type": "pointermove",
"source": "scope",
"markname": "dateGranularitySliderInteractive_rect",
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "1-((x(group())-columnsWidth-event.item.mark.group.bounds.x1+datum.bounds.x1)/configDateStepSlider.track.width)"
}
]
},
{
"name": "xDomainPreliminary",
"init": "xDomainInitial",
"on": [
{
"events": {"signal": "zoom"},
"update": "wheelDelta === 0 || resetAnimationTEased !== 1 ? xDomainPreliminary : [currentDomainCenter + (xDomain[0] - currentDomainCenter) * zoom, currentDomainCenter + (xDomain[1] - currentDomainCenter) * zoom]"
},
{
"events": {"signal": "granularityPercentage"},
"update": "[round(currentDomainCenter - (granularityPercentage*span(domain('xScaleHorizontalScrollMap')))/2), round(currentDomainCenter + (granularityPercentage*span(domain('xScaleHorizontalScrollMap')))/2)]"
},
{
"events": {"signal": "horizontalScrollPercentage.current"},
"update": "[scale('scaleXScroll', horizontalScrollPercentage.current) - span(xDomain)/2, scale('scaleXScroll', horizontalScrollPercentage.current) + span(xDomain)/2]"
},
{
"events": {"signal": "resetAnimationBounds"},
"update": "!isValid(resetAnimationBounds) ? xDomainPreliminary : [lerp([resetAnimationBounds.xDomain[0], xDomainInitial[0]], resetAnimationTEased), lerp([resetAnimationBounds.xDomain[1], xDomainInitial[1]], resetAnimationTEased)]"
}
]
},
{"name": "today", "update": "utc(year(now()),month(now()),date(now()))"},
{
"name": "currentXBandwidth",
"update": "(round((scale('x', timeOffset('hours', utchours(0,0,0,0),1)) - scale('x', utchours(0,0,0,0))) * 100)/100) * (ganttWidth < 358 ? 4.5 : ganttWidth < 593 ? 2.5 : ganttWidth < 889 ? 1.5 : 1)"
},
{"name": "thresholdMinuteBandwidth", "update": "17.78"},
{"name": "thresholdHourBandwidth", "update": "0.6"},
{"name": "thresholdDayBandwidth", "update": "0.26"},
{"name": "thresholdMonthBandwidth", "update": "0.04"},
{"name": "thresholdYearBandwidth", "update": "0.03"},
{
"name": "xCurrentUnit",
"update": "(currentXBandwidth>=thresholdMinuteBandwidth ? 'hour': currentXBandwidth>=thresholdHourBandwidth ? 'day' : currentXBandwidth >= thresholdDayBandwidth ? 'month' : currentXBandwidth >= thresholdMonthBandwidth ? 'month' : 'year')"
},
{
"name": "xCurrentDateFormat",
"update": "['%H', '%d', currentXBandwidth >= 0.12 ? '%B %Y' : '%b', '%Y'][indexof(['hour', 'day', 'month', 'year'],xCurrentUnit)]"
},
{
"name": "xCurrentDateLabelDX",
"update": "xCurrentUnit === 'hour' ? ((scale('x', utchours(0,0,0,0))-scale('x', timeOffset('hours', utchours(0,0,0,0), -1)))/2) : (scale('x', now())-scale('x', timeOffset(xCurrentUnit, now(), -1)))/2"
},
{
"name": "xCurrentUnitOffset",
"update": "xCurrentUnit === 'year' ? null : ['hour', 'day', 'month', 'year'][indexof(['hour','day', 'month'], xCurrentUnit)+1]"
},
{
"name": "xCurrentDateFormatOffset",
"update": "['%H 🕐', '%d %b %Y 🕐', '%B %Y', '%Y'][indexof(['hour', 'day', 'month', 'year'],xCurrentUnitOffset)]"
},
{
"name": "xCurrentDateOffsetLabelDX",
"update": "xCurrentUnitOffset === 'hour' ? ((scale('x', utchours(0,0,0,0))-scale('x', timeOffset('hours', utchours(0,0,0,0), -1)))/2) : (scale('x', now())-scale('x', timeOffset(xCurrentUnitOffset, now(), -1)))/2"
},
{"name": "horizontalScrollMapHeight", "value": 10},
{
"name": "ganttHorizontalScrollMapMouseDown",
"description": "boolean indicating whether the horizontal scroll map scrollbar is currently being clicked",
"value": false,
"on": [
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:pointerdown",
"update": "true"
},
{"events": {"type": "pointerup"}, "update": "false"}
]
},
{
"name": "ganttHorizontalScrollMapMouseOver",
"description": "boolean indicating whether the horizontal scroll map scrollbar is currently being moused over",
"value": false,
"on": [
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:mouseover",
"update": "true"
},
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:mouseout",
"update": "false"
}
]
},
{
"name": "resetAnimationDuration",
"on": [
{
"events": "@granularity-reset-interactive-rect:click",
"update": "clamp(100 + pow(abs(span(xDomain) - span(xDomainInitial)) / span(xDomainInitial), 0.5) * 200 + pow(abs(horizontalScrollPercentage.current - horizontalScrollPercentage.previous), 0.5) * 2000, 250, 1000)"
}
]
},
{
"name": "resetAnimationBounds",
"init": "null",
"on": [
{
"events": "@granularity-reset-interactive-rect:click",
"update": "isValid(resetAnimationBounds) ? resetAnimationBounds : {start: now(), end: now()+resetAnimationDuration, t: 0, xDomain: isValid(resetAnimationBounds) ? null : xDomain}"
},
{
"events": {"signal": "timer"},
"update": "!isValid(resetAnimationBounds) ? null : now() > resetAnimationBounds.end ? null : {start: resetAnimationBounds.start, end: resetAnimationBounds.end, t: (now()-resetAnimationBounds.start)/resetAnimationDuration, xDomain: isValid(resetAnimationBounds) ? resetAnimationBounds.xDomain : xDomain}"
}
]
},
{
"name": "resetAnimationTEased",
"value": 1,
"on": [
{
"events": {"signal": "resetAnimationBounds"},
"update": "!isValid(resetAnimationBounds) ? 1 : resetAnimationBounds.t < 0.5 ? 4 * pow(resetAnimationBounds.t, 3) : 1 - pow(-2 * resetAnimationBounds.t + 2, 3) / 2"
}
]
},
{
"name": "granularityResetMouseover",
"description": "",
"value": false,
"on": [
{
"events": "@granularity-reset-interactive-rect:mouseover",
"update": "true"
},
{
"events": "@granularity-reset-interactive-rect:mouseout",
"update": "false"
}
]
},
{
"name": "padding",
"update": "{'top': 5, 'right': panAndZoomMode ? 5 : 5, 'bottom': ganttHorizontalScrollMapMouseDown ? 5 : 5, 'left': 5}"
},
{
"name": "activeScrollGroup",
"value": "verticalScrollbar",
"on": [
{
"events": "@group_verticalScrollbar:pointerdown",
"update": "'verticalScrollbar'"
},
{
"events": "@gantt-horizontal-scroll-map-interactive-rect:pointerdown",
"update": "'ganttHorizontalScrollMap'"
}
]
},
{"name": "height", "init": "min(adjustedHeight, actualHeight)"},
{"name": "actualHeight", "update": "blockHeight*length(data('dataset'))"},
{
"name": "timer",
"init": "now()",
"on": [{"events": "timer{20}", "update": "now()"}]
},
{
"name": "cursor",
"value": "default",
"on": [
{
"events": {
"type": "pointermove",
"source": "scope",
"consume": true,
"markname": "rect-gantt-background",
"between": [{"type": "pointerdown"}, {"type": "pointerup"}]
},
"update": "panAndZoomMode ? 'move' : 'default'"
},
{"events": "window:pointerup", "update": "'default'"}
]
}
],
"marks": [
{
"name": "group-header",
"type": "group",
"encode": {
"update": {
"x": {"value": 0},
"width": {"signal": "width"},
"height": {"signal": "20"},
"y": {"value": -50}
}
},
"marks": [
{
"name": "group_panAndZoomMode",
"description": "group for the marks that make up the toggle control to hide/show details",
"type": "group",
"interactive": false,
"clip": false,
"encode": {
"update": {
"y": {"value": 0},
"x": {"signal": "columnsWidth+ganttWidth"}
}
},
"marks": [
{
"name": "panAndZoomMode_text_zoom",
"description": "the title for the toggle control",
"type": "text",
"interactive": false,
"encode": {
"update": {
"x": {"signal": "0"},
"y": {"signal": "configTogglepanAndZoomMode.track.height/2"},
"text": {"signal": "'Pan & Zoom Mode'"},
"fontWeight": {
"signal": "panAndZoomMode ? configTogglepanAndZoomMode.on.fontWeight : configTogglepanAndZoomMode.label.fontWeight"
},
"baseline": {"value": "top"},
"font": {"signal": "configTogglepanAndZoomMode.label.font"},
"fontSize": {
"signal": "configTogglepanAndZoomMode.label.fontSize"
},
"fontStyle": {
"signal": "configTogglepanAndZoomMode.label.fontStyle"
},
"align": {"value": "right"},
"fill": {"signal": "configTogglepanAndZoomMode.label.fill"}
}
}
},
{
"name": "track_rect",
"description": "the track for the toggle control",
"type": "rect",
"from": {"data": "panAndZoomMode_text_zoom"},
"interactive": false,
"encode": {
"update": {
"y": {"signal": "(datum.bounds.y2-datum.bounds.y1)/2"},
"height": {
"signal": "configTogglepanAndZoomMode.track.height"
},
"x": {
"signal": "datum.bounds.x1-configTogglepanAndZoomMode.track.width-18"
},
"width": {"signal": "configTogglepanAndZoomMode.track.width"},
"cornerRadius": {
"signal": "configTogglepanAndZoomMode.track.cornerRadius"
},
"fill": {
"signal": "panAndZoomMode ? configTogglepanAndZoomMode.on.fill : configTogglepanAndZoomMode.track.fill"
},
"stroke": {
"signal": "configTogglepanAndZoomMode.track.stroke"
},
"strokeWidth": {
"signal": "configTogglepanAndZoomMode.track.strokeWidth"
}
}
}
},
{
"name": "toggle_outer_arc",
"description": "the circle mark that serves as the the 'toggle handle'",
"from": {"data": "track_rect"},
"type": "arc",
"interactive": false,
"encode": {
"enter": {
"y": {
"signal": "datum.bounds.y1+configTogglepanAndZoomMode.track.height/2"
},
"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": "panAndZoomMode ? datum.bounds.x2-configTogglepanAndZoomMode.track.height*0.9 : datum.bounds.x1+configTogglepanAndZoomMode.track.height*0.9"
}
}
}
},
{
"name": "panAndZoomMode_text_scroll",
"description": "the title for the toggle control",
"type": "text",
"from": {"data": "track_rect"},
"interactive": false,
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1"},
"dx": {"signal": "-configTogglepanAndZoomMode.label.dx"},
"y": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"text": {"signal": "'Scroll Mode'"},
"fontWeight": {
"signal": "panAndZoomMode ? configTogglepanAndZoomMode.label.fontWeight : configTogglepanAndZoomMode.on.fontWeight"
},
"baseline": {"value": "middle"},
"font": {"signal": "configTogglepanAndZoomMode.label.font"},
"fontSize": {
"signal": "configTogglepanAndZoomMode.label.fontSize"
},
"fontStyle": {
"signal": "configTogglepanAndZoomMode.label.fontStyle"
},
"align": {"value": "right"},
"fill": {"signal": "configTogglepanAndZoomMode.label.fill"}
}
}
}
]
},
{
"name": "mouseWheel-granularity-interactive-rect",
"type": "rect",
"from": {"data": "group_panAndZoomMode"},
"interactive": true,
"encode": {
"update": {
"y": {"signal": "datum.bounds.y1"},
"y2": {"signal": "datum.bounds.y2"},
"x": {"signal": "datum.bounds.x1-5"},
"x2": {"signal": "datum.bounds.x2"},
"fill": {"value": "#fff"},
"fillOpacity": {"value": 0.15},
"tooltip": {
"signal": "configTogglepanAndZoomMode.tooltip.object"
},
"cursor": {"value": "pointer"}
},
"hover": {"fill": {"value": "transparent"}}
}
}
]
},
{
"name": "rect-background",
"type": "rect",
"encode": {
"update": {
"width": {"signal": "columnsWidth+ganttWidth-1"},
"height": {"signal": "height"},
"fill": {"value": "transparent"},
"stroke": {"value": "#AAA"},
"strokeWidth": {"value": 0.1}
}
}
},
{
"name": "rect-gantt-background",
"type": "rect",
"encode": {
"update": {
"x": {"signal": "columnsWidth"},
"width": {"signal": "ganttWidth-1"},
"height": {"signal": "height"},
"fill": {"value": "transparent"},
"stroke": {"value": "#AAA"},
"strokeWidth": {"value": 0.1}
}
}
},
{
"name": "group-gantt",
"type": "group",
"clip": true,
"encode": {
"update": {
"x": {"signal": "columnsWidth"},
"y": {
"signal": "clamp(-verticalScrollPercentage*actualHeight,-(actualHeight-adjustedHeight), 0)"
},
"width": {"signal": "ganttWidth"},
"height": {"signal": "actualHeight"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "rect-weekends",
"type": "rect",
"from": {"data": "weekendDates"},
"encode": {
"update": {
"x": {"scale": "x", "field": "startDate"},
"x2": {"scale": "x", "field": "endDate"},
"height": {"signal": "actualHeight"},
"fill": {"value": "#fbfcfd"},
"opacity": {
"signal": "indexof(['%B %Y', '%d', '%H'], xCurrentDateFormat) >= 0 ? 1 : 0"
}
}
}
},
{
"name": "rect-time-series-bars",
"type": "rect",
"from": {"data": "dataset_formatted"},
"interactive": false,
"encode": {
"update": {
"x": {"scale": "x", "field": "startDate"},
"x2": {"scale": "x", "field": "endDate"},
"y": {"signal": "(datum.index-1)*blockHeight", "offset": 2.5},
"height": {"signal": "blockHeight", "offset": -2.5},
"fill": {"value": "#fbe4c7"},
"stroke": {"value": "#F0941F"}
}
}
}
]
},
{
"name": "group-gantt-horizontal-scroll-map",
"type": "group",
"encode": {
"update": {
"x": {"signal": "columnsWidth+ (ganttHorizontalScrollMapMouseDown ? 0 : 0)"},
"y": {"signal": "ganttHorizontalScrollMapMouseDown ? 0 : height+5"}
}
},
"marks": [
{
"name": "track",
"type": "rect",
"encode": {
"update": {
"x": {"signal": "ganttHorizontalScrollMapMouseDown ? 0 : 0"},
"width": {"signal": "range('xScaleHorizontalScrollMap')[1]"},
"y": {
"signal": "(ganttHorizontalScrollMapMouseDown ? height+5 : 0) + horizontalScrollMapHeight/2",
"offset": -0.15
},
"height": {"value": 0.3},
"fill": {"value": "#999"},
"opacity": {
"signal": "scale('xScaleHorizontalScrollMap', xDomain[0]) === range('xScaleHorizontalScrollMap')[0] ? 0 : 1"
}
}
}
},
{
"name": "rect-domain-max-brush",
"type": "rect",
"from": {"data": "track"},
"encode": {
"update": {
"y": {"signal": "datum.bounds.y1 - horizontalScrollMapHeight/2"},
"x": {
"scale": "xScaleHorizontalScrollMap",
"signal": "xDomain[0]",
"offset": {"signal": "(ganttHorizontalScrollMapMouseDown ? 0 : 0) -0.5"}
},
"x2": {
"scale": "xScaleHorizontalScrollMap",
"signal": "xDomain[1]",
"offset": {"signal": "(ganttHorizontalScrollMapMouseDown ? 0 : 0) + 0.5"}
},
"height": {"signal": "horizontalScrollMapHeight"},
"fill": {"signal": "ganttHorizontalScrollMapMouseOver ? '#f3f7fa' : '#fff'"},
"opacity": {
"signal": "scale('xScaleHorizontalScrollMap', xDomain[0]) === range('xScaleHorizontalScrollMap')[0] && scale('xScaleHorizontalScrollMap', xDomain[1]) === range('xScaleHorizontalScrollMap')[1] ? 0 : 1"
}
}
}
},
{
"name": "time-series-rect",
"type": "rect",
"from": {"data": "track"},
"encode": {
"update": {
"x": {
"signal": "datum.bounds.x1+scale('xScaleHorizontalScrollMap', xDomainInitial[0])"
},
"x2": {
"signal": "datum.bounds.x1+scale('xScaleHorizontalScrollMap', xDomainInitial[1])"
},
"y": {
"signal": "datum.bounds.y1-(datum.bounds.y2-datum.bounds.y1)/2"
},
"y2": {
"signal": "datum.bounds.y1+(datum.bounds.y2-datum.bounds.y1)/2"
},
"strokeWidth": {"signal": "horizontalScrollMapHeight/4"},
"stroke": {"value": "#CCC"},
"fill": {"value": "transparent"},
"cursor": {
"signal": "(ganttHorizontalScrollMapMouseOver || ganttHorizontalScrollMapMouseDown) && scale('xScaleHorizontalScrollMap', xDomain[0]) !== range('xScaleHorizontalScrollMap')[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},
"opacity": {
"signal": "xDomain[0] <= scrollDomain[0] && xDomain[1] >= scrollDomain[1] ? 0 : 1"
}
}
}
},
{
"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},
"opacity": {
"signal": "xDomain[0] <= scrollDomain[0] && xDomain[1] >= scrollDomain[1] ? 0 : 1"
}
}
}
},
{
"name": "text-date-unit-label",
"type": "text",
"from": {"data": "track"},
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1"},
"y": {"signal": "datum.bounds.y1+15"},
"baseline": {"value": "top"},
"text": {
"signal": "upper(substring(xCurrentUnit, 0, 1))+substring(xCurrentUnit, 1, length(xCurrentUnit))+' View'"
},
"fontSize": {"value": 9},
"fill": {"value": "#666"}
}
}
},
{
"name": "text-date-granularity-reset-label",
"type": "text",
"from": {"data": "text-date-unit-label"},
"encode": {
"update": {
"x": {"signal": "ganttWidth-20"},
"y": {"signal": "datum.bounds.y1"},
"align": {"value": "right"},
"baseline": {"value": "top"},
"text": {"signal": "'Reset'"},
"fontSize": {"signal": "configDateStepSlider.label.fontSize"},
"fill": {"signal": "granularityResetMouseover ? '#222' : '#666'"}
}
}
},
{
"name": "text-date-granularity-reset-icon",
"type": "symbol",
"from": {"data": "text-date-granularity-reset-label"},
"encode": {
"update": {
"x": {"signal": "datum.bounds.x2 + 6.5"},
"y": {"signal": "datum.bounds.y1-2.5"},
"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": "group_dateGranularitySlider",
"description": "the group of marks that makes up the slider control",
"type": "group",
"from": {"data": "text-date-granularity-reset-label"},
"interactive": false,
"clip": false,
"encode": {
"update": {
"y": {"signal": "datum.bounds.y1"},
"height": {"signal": "configDateStepSlider.track.height"},
"x": {
"signal": "datum.bounds.x1-configDateStepSlider.track.cornerRadius*2"
},
"x2": {
"signal": "datum.bounds.x1-configDateStepSlider.track.width-configDateStepSlider.track.cornerRadius*2"
}
}
},
"marks": [
{
"name": "labelDateGranularitySlider_text",
"description": "the title for the slider control",
"type": "text",
"interactive": false,
"encode": {
"update": {
"y": {"signal": "configDateStepSlider.track.height/2"},
"text": {"signal": "configDateStepSlider.label.text || ''"},
"baseline": {"value": "middle"},
"font": {"signal": "configDateStepSlider.label.font"},
"fontSize": {"signal": "configDateStepSlider.label.fontSize"},
"fontStyle": {
"signal": "configDateStepSlider.label.fontStyle"
},
"align": {"value": "right"},
"dx": {
"signal": "-configDateStepSlider.track.cornerRadius*2"
},
"fill": {"signal": "configDateStepSlider.label.fill"}
}
}
},
{
"name": "track_rect",
"description": "the track for the slider control",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"y": {"signal": "0"},
"height": {"signal": "configDateStepSlider.track.height"},
"x": {"signal": "0"},
"x2": {"signal": "configDateStepSlider.track.width"},
"cornerRadius": {
"signal": "configDateStepSlider.track.cornerRadius"
},
"fill": {"signal": "configDateStepSlider.track.fill"},
"stroke": {"signal": "configDateStepSlider.track.stroke"},
"strokeWidth": {
"signal": "configDateStepSlider.track.strokeWidth"
}
}
}
},
{
"name": "sliderPercentage_rect",
"description": "the rect that indicates the slider percentage",
"type": "rect",
"from": {"data": "labelDateGranularitySlider_text"},
"interactive": false,
"encode": {
"update": {
"height": {"signal": "configDateStepSlider.track.height"},
"x": {"value": 0},
"x2": {
"signal": "scale('xSliderGranularityPercentage', span(xDomain)/span(domain('xScaleHorizontalScrollMap')))"
},
"cornerRadius": {
"signal": "configDateStepSlider.track.cornerRadius"
},
"fill": {"signal": "configDateStepSlider.progress.fill"},
"fillOpacity": {
"signal": "configDateStepSlider.progress.fillOpacity"
},
"stroke": {"signal": "configDateStepSlider.track.stroke"},
"strokeWidth": {
"signal": "configDateStepSlider.track.strokeWidth"
}
}
}
},
{
"name": "dateGranularitySliderInteractive_rect",
"description": "the invisible rect that is used for capturing events related to the control",
"from": {"data": "labelDateGranularitySlider_text"},
"type": "rect",
"interactive": true,
"encode": {
"update": {
"y": {
"signal": "dateGranularitySliderMouseDown ? -configDateStepSlider.track.height*6 : 0"
},
"x": {
"signal": "dateGranularitySliderMouseDown ? -configDateStepSlider.track.width/4 : 0"
},
"width": {
"signal": "configDateStepSlider.track.width + (dateGranularitySliderMouseDown ? configDateStepSlider.track.width/2 : 0)"
},
"height": {
"signal": "dateGranularitySliderMouseDown ? configDateStepSlider.track.height*10 : configDateStepSlider.track.height"
},
"fill": {"value": "transparent"},
"cursor": {"value": "pointer"},
"tooltip": {
"signal": "dateGranularitySliderMouseDown ? '' : configDateStepSlider.tooltip.text"
}
}
}
},
{
"name": "handles_outer_arc",
"description": "the outer circle mark that serves as the the 'slider handle'",
"from": {"data": "labelDateGranularitySlider_text"},
"type": "arc",
"interactive": false,
"encode": {
"enter": {
"y": {"signal": "configDateStepSlider.track.height/2"},
"innerRadius": {"value": 0},
"outerRadius": {
"signal": "configDateStepSlider.track.height*0.9"
},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "configDateStepSlider.handle.outerStroke || '#BBB'"
},
"strokeWidth": {
"signal": "configDateStepSlider.handle.outerStrokeWidth"
},
"fill": {
"signal": "configDateStepSlider.handle.outerFill || '#fff'"
}
},
"update": {
"x": {
"signal": "scale('xSliderGranularityPercentage', span(xDomain)/span(domain('xScaleHorizontalScrollMap')))"
}
}
}
},
{
"name": "handles_inner_arc",
"description": "the inner circle mark that serves as the the 'slider handle'",
"type": "arc",
"from": {"data": "labelDateGranularitySlider_text"},
"interactive": false,
"encode": {
"enter": {
"y": {"signal": "configDateStepSlider.track.height/2"},
"innerRadius": {"value": 0},
"outerRadius": {"signal": "1"},
"startAngle": {"signal": "0"},
"endAngle": {"signal": "2*PI"},
"stroke": {
"signal": "configDateStepSlider.handle.innerStroke"
},
"fill": {
"signal": "configDateStepSlider.handle.innerFill || '#999'"
}
},
"update": {
"x": {
"signal": "scale('xSliderGranularityPercentage', span(xDomain)/span(domain('xScaleHorizontalScrollMap')))"
}
}
}
}
]
},
{
"name": "gantt-horizontal-scroll-map-interactive-rect",
"type": "rect",
"from": {"data": "track"},
"encode": {
"update": {
"x": {"signal": "ganttHorizontalScrollMapMouseDown ? 0 : datum.bounds.x1"},
"x2": {
"signal": "dateGranularitySliderMouseDown || panAndZoomMode ? 0 : datum.bounds.x2"
},
"y": {
"signal": "ganttHorizontalScrollMapMouseDown ? 0 : datum.bounds.y1 - horizontalScrollMapHeight/2-5"
},
"y2": {
"signal": "datum.bounds.y2 + horizontalScrollMapHeight/2+5 + (ganttHorizontalScrollMapMouseDown ? 18 : 0)"
},
"fillOpacity": {"value": 0},
"cursor": {
"signal": "scale('xScaleHorizontalScrollMap', xDomain[0]) === range('xScaleHorizontalScrollMap')[0] ? 'default' : ganttHorizontalScrollMapMouseDown ? 'pointer' : ganttHorizontalScrollMapMouseOver ? 'pointer' : 'default'"
}
}
}
},
{
"name": "granularity-reset-interactive-rect",
"type": "rect",
"from": {"data": "text-date-granularity-reset-label"},
"encode": {
"update": {
"x": {"signal": "datum.bounds.x1-5"},
"x2": {"signal": "datum.bounds.x2+(ganttWidth-datum.bounds.x2)"},
"y": {"signal": "datum.bounds.y1-5"},
"y2": {"signal": "datum.bounds.y2+5"},
"fillOpacity": {"value": 0},
"cursor": {
"signal": "granularityResetMouseover ? 'pointer' : 'default'"
},
"tooltip": {"value": "Reset date granularity"}
}
}
}
]
},
{
"name": "group_verticalScrollbar",
"description": "the group of marks that make up the vertical scrollbar",
"type": "group",
"encode": {
"update": {
"x": {
"signal": "verticalScrollbarMouseDown ? 0 : width-configVerticalScrollbar.track.width"
},
"width": {
"signal": "actualHeight > adjustedHeight && configVerticalScrollbar.enabled ? verticalScrollbarMouseDown ? width+configVerticalScrollbar.track.width : configVerticalScrollbar.track.width : 0"
},
"y": {"signal": "configVerticalScrollbar.enabled ? -30 : 0"},
"height": {
"signal": "actualHeight > adjustedHeight && configVerticalScrollbar.enabled ? configVerticalScrollbar.track.height + horizontalScrollMapHeight*3 + 30 : 0"
},
"fill": {"value": "transparent"},
"cursor": {"signal": "panAndZoomMode ? 'default' : 'pointer'"},
"clip": {"value": true}
}
},
"marks": [
{
"name": "rect_verticalScrollbar_track",
"description": "the track for the scrollbar",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "verticalScrollbarMouseDown ? width-configVerticalScrollbar.track.width : 0"
},
"width": {
"signal": "configVerticalScrollbar.enabled ? configVerticalScrollbar.track.width : 0"
},
"y": {"value": 30},
"height": {
"signal": "configVerticalScrollbar.enabled ? configVerticalScrollbar.track.height : 0"
},
"fill": {"signal": "configVerticalScrollbar.track.fill"},
"fillOpacity": {
"signal": "panAndZoomMode ? 0.25 : 1"
},
"cursor": {"value": "pointer"}
}
}
},
{
"name": "rect_verticalScrollbar_handle",
"description": "the handle for the scrollbar",
"type": "rect",
"interactive": false,
"encode": {
"update": {
"x": {
"signal": "verticalScrollbarMouseDown ? width-configVerticalScrollbar.track.width : 0"
},
"width": {
"signal": "configVerticalScrollbar.enabled ? configVerticalScrollbar.track.width : 0"
},
"y": {
"signal": "scale('scaleScrollHandleY', verticalScrollPercentage)-configVerticalScrollbar.handle.height+30"
},
"y2": {
"signal": "scale('scaleScrollHandleY', verticalScrollPercentage)+30"
},
"fill": {
"signal": "verticalScrollbarMouseOver ? configVerticalScrollbar.handle.hover.fill : configVerticalScrollbar.handle.fill"
},
"fillOpacity": {
"signal": "panAndZoomMode ? 0.25 : 1"
},
"cursor": {"value": "pointer"}
}
}
}
]
}
],
"scales": [
{
"name": "dateUnitIncrementMS",
"type": "ordinal",
"domain": ["hours", "day", "month", "year"],
"range": [3600000, 86400000, 2628000000, 31540000000]
},
{
"name": "x",
"type": "time",
"domain": {"signal": "xDomain"},
"range": {"signal": "[0,ganttWidth]"}
},
{
"name": "scaleXScroll",
"type": "linear",
"clamp": true,
"domain": {"signal": "[0, 1]"},
"range": {"signal": "scrollDomain"}
},
{
"name": "xScaleHorizontalScrollMap",
"type": "time",
"clamp": true,
"domain": {"signal": "scrollDomain"},
"range": {"signal": "[1, ganttWidth-2]"}
},
{
"name": "xSliderGranularityPercentage",
"type": "time",
"clamp": true,
"domain": {"signal": "[1,0]"},
"range": {"signal": "[0, configDateStepSlider.track.width]"}
},
{
"name": "scaleScrollHandleY",
"type": "linear",
"domain": [0, {"signal": "(actualHeight-adjustedHeight)/actualHeight"}],
"range": {
"signal": "[configVerticalScrollbar.handle.height, configVerticalScrollbar.track.height]"
},
"clamp": true
}
],
"axes": [
{
"description": "Bottom date axis",
"ticks": true,
"labelPadding": {"signal": "xCurrentDateFormat === '%B %Y' ? 2 :-12"},
"scale": "x",
"position": {"signal": "columnsWidth"},
"orient": "top",
"domainColor": "#CCC",
"domainWidth": 0.25,
"tickSize": 15,
"tickOpacity": 1,
"grid": true,
"zindex": -2,
"labelOverlap": true,
"formatType": "time",
"labelBound": true,
"tickCount": {
"signal": "xCurrentUnit === 'hour' ? 24*(span(xDomain)/86400000) : xCurrentUnit"
},
"format": {"signal": "xCurrentDateFormat"},
"labelSeparation": 3,
"tickExtra": true,
"encode": {
"labels": {
"update": {
"dx": {"signal": "xCurrentDateLabelDX"},
"text": {
"signal": "xCurrentDateFormat === '%B %Y' ? split(datum.label, ' ') : datum.label"
}
}
},
"ticks": {
"update": {
"stroke": {"value": "#CCC"},
"strokeWidth": {
"signal": "xCurrentUnit === 'day' && datum.label === '01' ? 0 : indexof(datum.label, 'Jan') >= 0 ? 0 : 0.35"
}
}
},
"grid": {
"update": {
"stroke": {"value": "#CCC"},
"strokeWidth": {
"signal": "xCurrentUnit === 'day' && datum.label === '01' ? 0 : indexof(datum.label, 'Jan') >= 0 ? 0 : 0.35"
}
}
}
}
},
{
"description": "Top date axis",
"title": {
"signal": "xCurrentDateFormatOffset === '%B %Y' && (toDate(utcFormat((xDomain[0] + (xDomain[1]-xDomain[0])/2), '01-%B-%Y')) < domain('x')[0]) ? utcFormat((xDomain[0] + (xDomain[1]-xDomain[0])/2), '%B %Y') : null"
},
"titleX": {"signal": "ganttWidth/2"},
"titleY": -25,
"titleBaseline": {"value": "middle"},
"titleFontSize": {"value": 10},
"titleFontWeight": "400",
"scale": "x",
"position": {"signal": "columnsWidth"},
"domain": false,
"orient": "top",
"offset": 0,
"tickSize": 15,
"tickOpacity": 1,
"tickWidth": 0.5,
"tickColor": "#666",
"labelBaseline": "bottom",
"grid": true,
"gridWidth": 0.5,
"gridColor": {"value": "#666"},
"zindex": -1,
"labelPadding": 5,
"labelOverlap": true,
"formatType": "time",
"labelBound": true,
"tickCount": {"signal": "(xCurrentUnitOffset || 0)"},
"format": {"signal": "xCurrentDateFormatOffset"},
"labelSeparation": 5,
"encode": {
"labels": {
"update": {
"text": {
"signal": "xCurrentDateFormat === '%B %Y' ? null : datum.label"
},
"x": {
"signal": "xCurrentDateFormatOffset === '%Y' ? toDate(utcFormat(datum.value, '01-Jun-%Y')) > xDomain[1] ? scale('x', datum.value)+(scale('x', xDomain[1])-scale('x', datum.value))/2 : scale('x', toDate(utcFormat(datum.value, '01-Jun-%Y'))) : xCurrentDateFormatOffset === '%B %Y' ? toDate(utcFormat(datum.value, '17-%b-%Y')) > xDomain[1] ? scale('x', datum.value)+(scale('x', xDomain[1])-scale('x', datum.value))/2 : scale('x', toDate(utcFormat(datum.value, '15-%b-%Y'))) : scale('x', datum.value)"
},
"dx": {
"signal": "xCurrentDateFormatOffset === '%Y' || xCurrentDateFormatOffset === '%B %Y' ? 0 : xCurrentDateOffsetLabelDX"
},
"align": {"value": "center"}
}
}
}
}
],
"data": [
{
"name": "dataset",
"values": [{"startDate": null}],
"transform": [
{"type": "formula", "expr": "offsetUnit", "as": "offsetUnit"},
{
"type": "formula",
"expr": "negativeDateOffsetFromToday",
"as": "negativeDateOffsetFromToday"
},
{
"type": "formula",
"expr": "positiveOffsetFromToday",
"as": "positiveOffsetFromToday"
},
{"type": "formula", "expr": "recordCount", "as": "recordCount"},
{"type": "formula", "expr": "todayDate", "as": "startDate"},
{
"type": "formula",
"expr": "timeOffset(datum.offsetUnit, datum.startDate, datum.negativeDateOffsetFromToday)",
"as": "startDate"
},
{
"type": "formula",
"expr": "timeOffset(datum.offsetUnit, todayDate, datum.positiveOffsetFromToday)",
"as": "endDate"
},
{
"type": "formula",
"expr": "(datum.endDate-datum.startDate)/datum.recordCount",
"as": "recordIncrement"
},
{
"type": "formula",
"expr": "sequence(+datum.startDate+datum.recordIncrement/2, +datum.endDate+datum.recordIncrement, datum.recordIncrement/2)",
"as": "endDate"
},
{"type": "flatten", "fields": ["endDate"], "as": ["endDate"]},
{"type": "formula", "expr": "+datum.startDate", "as": "startDate"},
{"type": "window", "ops": ["row_number"], "as": ["index"]},
{
"type": "formula",
"expr": "datum.endDate<todayDate ? null : datum.endDate-todayDate",
"as": "afterTodayDuration"
},
{
"type": "window",
"ops": ["sum"],
"fields": ["afterTodayDuration"],
"as": ["aggAfterTodayDuration"]
},
{
"type": "formula",
"expr": "datum.afterTodayDuration === datum.aggAfterTodayDuration ? datum.startDate : (random()*(datum.endDate-datum.startDate))+datum.startDate",
"as": "startDate"
},
{
"type": "formula",
"expr": "utcFormat(datum.startDate, '%Y-%m-%dT%H:%M:%S.%LZ')",
"as": "startDateFormatted"
},
{
"type": "formula",
"expr": "utcFormat(datum.endDate, '%Y-%m-%dT%H:%M:%S.%LZ')",
"as": "endDateFormatted"
},
{
"type": "formula",
"expr": "datum.startDate+' '+datum.endDate",
"as": "sort"
},
{"type": "collect", "sort": {"field": "index", "order": "ascending"}}
]
},
{
"name": "dataset_formatted",
"source": "dataset",
"transform": [
{
"type": "formula",
"expr": "(datum.index-1)*blockHeight",
"as": "unadjustedY"
},
{
"type": "formula",
"expr": "clamp(verticalScrollPercentage*actualHeight,0, (actualHeight-adjustedHeight))",
"as": "viewportY"
},
{
"type": "formula",
"expr": "datum.viewportY + adjustedHeight",
"as": "viewportY2"
},
{
"type": "filter",
"expr": "inrange(datum.unadjustedY, [datum.viewportY, datum.viewportY2])"
}
]
},
{
"name": "xDomainInitial",
"source": "dataset",
"transform": [
{
"type": "aggregate",
"fields": ["startDate", "endDate"],
"ops": ["min", "max"],
"as": ["minDate", "maxDate"]
},
{
"type": "formula",
"expr": "[datum.minDate-scale('dateUnitIncrementMS', 'hours')*6,datum.maxDate+scale('dateUnitIncrementMS', 'hours')*6]",
"as": "domain"
},
{
"type": "formula",
"expr": "[utcFormat(datum.domain[0], '%d-%b-%Y %H:%M:%S.%LZ'), utcFormat(datum.domain[1], '%d-%b-%Y %H:%M:%S.%LZ')]",
"as": "domainFormatted"
},
{
"type": "formula",
"expr": "[datum.minDate-scale('dateUnitIncrementMS', 'hours'), datum.minDate + ((ganttWidth-minDateBandwidth)/minDateBandwidth)*scale('dateUnitIncrementMS', 'hours')]",
"as": "hourDomain"
},
{
"type": "formula",
"expr": "[datum.minDate-scale('dateUnitIncrementMS', 'day'), datum.minDate + ((ganttWidth-minDateBandwidth)/minDateBandwidth)*scale('dateUnitIncrementMS', 'day')]",
"as": "dayDomain"
},
{
"type": "formula",
"expr": "ceil(span(datum.domain)/scale('dateUnitIncrementMS', 'year'))",
"as": "yearCount"
}
]
},
{
"name": "weekendDates",
"values": [{}],
"transform": [
{
"type": "formula",
"expr": "timeSequence('day', xDomain[0], xDomain[1])",
"as": "date"
},
{"type": "flatten", "fields": ["date"]},
{"type": "formula", "expr": "+datum.date", "as": "startDate"},
{
"type": "formula",
"expr": "+datum.date + (scale('dateUnitIncrementMS', 'day')-1)",
"as": "endDate"
},
{
"type": "formula",
"expr": "indexof([0,6], day(datum.date)) >= 0",
"as": "isWeekend"
},
{"type": "filter", "expr": "datum.isWeekend"}
]
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment