Last active
March 3, 2025 15:30
-
-
Save Giammaria/bf2d8a0ab4caf6086173d61cdb05a382 to your computer and use it in GitHub Desktop.
20250218_basic_zoom_and_pan_v1_v
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "$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