Last active
April 21, 2026 13:50
-
-
Save Giammaria/f966835fbeed4e31581fb570ac98d786 to your computer and use it in GitHub Desktop.
20260420_RAD_Schedule
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/v6.json", | |
| "description": "RAD schedule prototype v2. Dynamic swim lane heights, minimap brush handles for horizontal zoom/pan, sticky two-tier fiscakl axis, clipping main mark area.", | |
| "width": 1450, | |
| "height": 740, | |
| "padding": 12, | |
| "autosize": {"type": "pad", "contains": "padding"}, | |
| "background": "#FFFFFF", | |
| "signals": [ | |
| { | |
| "name": "leftLabelWidth", | |
| "value": 250, | |
| "description": "Width of the fixed left pane that holds the workstream labels." | |
| }, | |
| { | |
| "name": "scrollbarWidth", | |
| "update": "contentHeight <= rowViewportHeight ? 0 : 12", | |
| "description": "Width of the custom vertical scrollbar rendered beside the Gantt canvas." | |
| }, | |
| { | |
| "name": "headerHeight", | |
| "value": 52, | |
| "description": "Height of the sticky two-tier time axis header." | |
| }, | |
| { | |
| "name": "legendHeight", | |
| "value": 50, | |
| "description": "Reserved height for the legend region at the bottom of the chart." | |
| }, | |
| { | |
| "name": "miniMapHeight", | |
| "value": 92, | |
| "description": "Total height reserved for the overview mini-map area." | |
| }, | |
| { | |
| "name": "mainChartTop", | |
| "update": "headerHeight", | |
| "description": "Top y-coordinate where the scrollable Gantt body begins." | |
| }, | |
| { | |
| "name": "miniMapInnerPadTop", | |
| "update": "miniMapLabelPadTop + 18", | |
| "description": "Top padding between the mini-map controls and the mini-map drawing area." | |
| }, | |
| { | |
| "name": "miniMapInnerHeight", | |
| "update": "miniMapHeight - miniMapInnerPadTop - 8", | |
| "description": "Drawable height of the mini-map after internal padding is removed." | |
| }, | |
| { | |
| "name": "mainTimelineWidth", | |
| "update": "width - leftLabelWidth - scrollbarWidth - 8", | |
| "description": "Width of the timeline canvas to the right of the fixed workstream pane." | |
| }, | |
| { | |
| "name": "rowViewportHeight", | |
| "update": "height - headerHeight - legendHeight - miniMapHeight - 30", | |
| "description": "Maximum available height for the Gantt viewport before vertical scrolling is required." | |
| }, | |
| { | |
| "name": "barHeight", | |
| "value": 8, | |
| "description": "Stroke thickness used for phase duration bars." | |
| }, | |
| { | |
| "name": "nodeStep", | |
| "value": 32, | |
| "description": "Vertical spacing allocated for each item row within a swim lane." | |
| }, | |
| { | |
| "name": "laneTopPad", | |
| "value": 24, | |
| "description": "Top padding inside each swim lane before the first item row." | |
| }, | |
| { | |
| "name": "minLaneHeight", | |
| "value": 56, | |
| "description": "Minimum height enforced for a swim lane even when it has little content." | |
| }, | |
| { | |
| "name": "labelDy", | |
| "value": 10, | |
| "description": "Vertical offset used to position milestone and cadence labels above their marks." | |
| }, | |
| { | |
| "name": "milestoneSize", | |
| "value": 120, | |
| "description": "Size of milestone diamond symbols in the main chart." | |
| }, | |
| { | |
| "name": "cadenceSymbolSize", | |
| "value": 75, | |
| "description": "Size of cadence milestone symbols in the main chart and mini-map." | |
| }, | |
| { | |
| "name": "fiscalYearStartMonth", | |
| "value": 10, | |
| "description": "Fiscal year start month used to calculate fiscal year and quarter labels." | |
| }, | |
| { | |
| "name": "fiscalYearStartDay", | |
| "value": 1, | |
| "description": "Fiscal year start day within the fiscal year start month." | |
| }, | |
| { | |
| "name": "showMiniMap", | |
| "value": true, | |
| "description": "Boolean toggle that controls whether the overview mini-map is displayed." | |
| }, | |
| { | |
| "name": "showLegend", | |
| "value": true, | |
| "description": "Boolean toggle that controls whether the legend is displayed." | |
| }, | |
| { | |
| "name": "showTodayLine", | |
| "value": true, | |
| "description": "Boolean toggle that controls whether the today reference line is displayed." | |
| }, | |
| { | |
| "name": "today", | |
| "update": "utc(year(now()), month(now()), date(now()))", | |
| "description": "Current date used to position the today reference line." | |
| }, | |
| { | |
| "name": "monthBandStart", | |
| "description": "UTC month boundary used as the starting point for generated month bands, extended backward to preserve edge quarter labels.", | |
| "update": "utcOffset('month', utc(utcyear(fullDomain[0]), utcmonth(fullDomain[0]), 1), -2)" | |
| }, | |
| { | |
| "name": "monthBandStop", | |
| "description": "UTC month boundary used as the exclusive stopping point for generated month bands, extended forward to preserve edge quarter labels.", | |
| "update": "utcOffset('month', utc(utcyear(fullDomain[1]), utcmonth(fullDomain[1]), 1), 3)" | |
| }, | |
| { | |
| "name": "monthBandCount", | |
| "description": "Number of generated month bands between monthBandStart and monthBandStop.", | |
| "update": "(utcyear(monthBandStop) - utcyear(monthBandStart)) * 12 + (utcmonth(monthBandStop) - utcmonth(monthBandStart))" | |
| }, | |
| { | |
| "name": "categoryDomain", | |
| "value": [ | |
| "Events", | |
| "Contracts", | |
| "Deliveries", | |
| "Testing & Evaluation", | |
| "Development & Documentation" | |
| ], | |
| "description": "Configured domain of workstream/category values used by the color scale." | |
| }, | |
| { | |
| "name": "categoryRange", | |
| "value": ["#7E22CE", "#9333EA", "#65A30D", "#EAB308", "#DC2626"], | |
| "description": "Configured color palette mapped to the workstream/category domain." | |
| }, | |
| { | |
| "name": "contentHeight", | |
| "update": "length(data('contentStats')) ? data('contentStats')[0].contentHeight : 0", | |
| "description": "Computed full height of all swim lanes before viewport clipping." | |
| }, | |
| { | |
| "name": "maxYScroll", | |
| "update": "max(contentHeight - rowViewportHeight, 0)", | |
| "description": "Maximum vertical scroll offset available for the Gantt body." | |
| }, | |
| { | |
| "name": "scrollTrackHeight", | |
| "update": "ganttCanvasHeight", | |
| "description": "Visible height of the custom vertical scrollbar track." | |
| }, | |
| { | |
| "name": "scrollHandleHeight", | |
| "update": "max((ganttCanvasHeight / max(contentHeight,1)) * ganttCanvasHeight, 30)", | |
| "description": "Computed height of the scrollbar handle based on visible content versus total content." | |
| }, | |
| { | |
| "name": "minBrushWidthPct", | |
| "value": 0.1, | |
| "description": "Minimum width of the mini-map selection window as a percentage of the full timeline." | |
| }, | |
| { | |
| "name": "brushStartPct", | |
| "value": 0, | |
| "on": [ | |
| { | |
| "events": "window:pointermove", | |
| "update": "dragMode === 'left' ? clamp(brushInitStartPct + (x() - brushDragStartX)/mainTimelineWidth, 0, brushInitEndPct - minBrushWidthPct) : dragMode === 'window' ? clamp(brushInitStartPct + (x() - brushDragStartX)/mainTimelineWidth, 0, 1 - (brushInitEndPct - brushInitStartPct)) : brushStartPct" | |
| }, | |
| { | |
| "events": "@minimap_background:click", | |
| "update": "clamp(((x() - leftLabelWidth)/mainTimelineWidth) - ((brushEndPct - brushStartPct)/2), 0, 1 - (brushEndPct - brushStartPct))" | |
| }, | |
| {"events": "@minimap_reset_button:click", "update": "0"} | |
| ], | |
| "description": "Starting percentage of the mini-map brush selection relative to the full timeline." | |
| }, | |
| { | |
| "name": "brushEndPct", | |
| "value": 1, | |
| "on": [ | |
| { | |
| "events": "window:pointermove", | |
| "update": "dragMode === 'right' ? clamp(brushInitEndPct + (x() - brushDragStartX)/mainTimelineWidth, brushInitStartPct + minBrushWidthPct, 1) : dragMode === 'window' ? clamp(brushInitEndPct + (x() - brushDragStartX)/mainTimelineWidth, (brushInitEndPct - brushInitStartPct), 1) : brushEndPct" | |
| }, | |
| { | |
| "events": "@minimap_background:click", | |
| "update": "clamp(((x() - leftLabelWidth)/mainTimelineWidth) + ((brushEndPct - brushStartPct)/2), (brushEndPct - brushStartPct), 1)" | |
| }, | |
| {"events": "@minimap_reset_button:click", "update": "1"} | |
| ], | |
| "description": "Ending percentage of the mini-map brush selection relative to the full timeline." | |
| }, | |
| { | |
| "name": "dragMode", | |
| "value": null, | |
| "on": [ | |
| {"events": "@minimap_window:pointerdown", "update": "'window'"}, | |
| {"events": "@minimap_left_handle:pointerdown", "update": "'left'"}, | |
| {"events": "@minimap_right_handle:pointerdown", "update": "'right'"}, | |
| {"events": "window:pointerup", "update": "null"} | |
| ], | |
| "description": "Current interaction mode for the mini-map brush, such as moving the window or dragging a handle." | |
| }, | |
| { | |
| "name": "brushDragStartX", | |
| "value": 0, | |
| "on": [ | |
| { | |
| "events": [ | |
| { | |
| "source": "scope", | |
| "type": "pointerdown", | |
| "markname": "minimap_window" | |
| }, | |
| { | |
| "source": "scope", | |
| "type": "pointerdown", | |
| "markname": "minimap_left_handle" | |
| }, | |
| { | |
| "source": "scope", | |
| "type": "pointerdown", | |
| "markname": "minimap_right_handle" | |
| } | |
| ], | |
| "update": "x()" | |
| } | |
| ], | |
| "description": "Pointer x-position captured when a mini-map drag interaction begins." | |
| }, | |
| { | |
| "name": "brushInitStartPct", | |
| "value": 0, | |
| "on": [ | |
| { | |
| "events": [ | |
| { | |
| "source": "scope", | |
| "type": "pointerdown", | |
| "markname": "minimap_window" | |
| }, | |
| { | |
| "source": "scope", | |
| "type": "pointerdown", | |
| "markname": "minimap_left_handle" | |
| }, | |
| { | |
| "source": "scope", | |
| "type": "pointerdown", | |
| "markname": "minimap_right_handle" | |
| } | |
| ], | |
| "update": "brushStartPct" | |
| } | |
| ], | |
| "description": "Brush start percentage captured at the beginning of a drag interaction." | |
| }, | |
| { | |
| "name": "brushInitEndPct", | |
| "value": 1, | |
| "on": [ | |
| { | |
| "events": [ | |
| { | |
| "source": "scope", | |
| "type": "pointerdown", | |
| "markname": "minimap_window" | |
| }, | |
| { | |
| "source": "scope", | |
| "type": "pointerdown", | |
| "markname": "minimap_left_handle" | |
| }, | |
| { | |
| "source": "scope", | |
| "type": "pointerdown", | |
| "markname": "minimap_right_handle" | |
| } | |
| ], | |
| "update": "brushEndPct" | |
| } | |
| ], | |
| "description": "Brush end percentage captured at the beginning of a drag interaction." | |
| }, | |
| { | |
| "name": "zoomSpanPct", | |
| "update": "brushEndPct - brushStartPct", | |
| "description": "Current width of the mini-map brush selection as a percentage of the full timeline." | |
| }, | |
| { | |
| "name": "zoomMode", | |
| "update": "zoomSpanPct > 0.42 ? 'fy_quarter' : 'fy_quarter_month'", | |
| "description": "Derived label-density mode for the x-axis based on the current zoom span." | |
| }, | |
| { | |
| "name": "fullDomain", | |
| "update": "[toDate(toNumber(fullDomainRaw[0]) - axisPadMs), toDate(toNumber(fullDomainRaw[1]) + axisPadMs)]", | |
| "description": "Padded full timeline domain used for the overview scale and brush calculations." | |
| }, | |
| { | |
| "name": "xDomain", | |
| "update": "[toDate(toNumber(fullDomain[0]) + (toNumber(fullDomain[1]) - toNumber(fullDomain[0])) * brushStartPct), toDate(toNumber(fullDomain[0]) + (toNumber(fullDomain[1]) - toNumber(fullDomain[0])) * brushEndPct)]", | |
| "description": "Current visible x-domain derived from the padded full domain and mini-map brush extent." | |
| }, | |
| { | |
| "name": "isMouseWheel", | |
| "on": [{"events": "wheel", "update": "abs(event.deltaY) >= 120"}] | |
| }, | |
| { | |
| "name": "yScroll", | |
| "value": 0, | |
| "on": [ | |
| { | |
| "events": "view:wheel!", | |
| "update": "clamp(yScroll + ((isMouseWheel ? 1 : -1) * event.deltaY*0.25), 0, maxYScroll)" | |
| }, | |
| { | |
| "events": "window:pointermove", | |
| "update": "vScrollDrag ? clamp(vScrollInit + (y() - vScrollStartY) * (maxYScroll / max(scrollTrackHeight - scrollHandleHeight,1)), 0, maxYScroll) : yScroll" | |
| }, | |
| { | |
| "events": "@vertical_scrollbar_track:pointerdown", | |
| "update": "clamp((y() - mainChartTop - scrollHandleHeight/2) * (maxYScroll / max(scrollTrackHeight - scrollHandleHeight,1)), 0, maxYScroll)" | |
| } | |
| ], | |
| "description": "Current vertical scroll offset applied to the Gantt rows." | |
| }, | |
| { | |
| "name": "vScrollDrag", | |
| "value": false, | |
| "on": [ | |
| { | |
| "events": "@vertical_scrollbar_handle:pointerdown", | |
| "update": "maxYScroll > 0" | |
| }, | |
| {"events": "window:pointerup", "update": "false"} | |
| ], | |
| "description": "Boolean flag indicating whether the custom vertical scrollbar handle is being dragged." | |
| }, | |
| { | |
| "name": "vScrollStartY", | |
| "value": 0, | |
| "on": [ | |
| {"events": "@vertical_scrollbar_handle:pointerdown", "update": "y()"} | |
| ], | |
| "description": "Pointer y-position captured when vertical scrollbar dragging begins." | |
| }, | |
| { | |
| "name": "vScrollInit", | |
| "value": 0, | |
| "on": [ | |
| { | |
| "events": "@vertical_scrollbar_handle:pointerdown", | |
| "update": "yScroll" | |
| } | |
| ], | |
| "description": "Initial vertical scroll offset captured at the start of a scrollbar drag." | |
| }, | |
| { | |
| "name": "overviewYDomain", | |
| "update": "[0, contentHeight]", | |
| "description": "Domain used by the overview mini-map y-scale." | |
| }, | |
| { | |
| "name": "miniMapLabelPadTop", | |
| "value": 22, | |
| "description": "Vertical position used for the mini-map title and reset control." | |
| }, | |
| { | |
| "name": "xDomainPadPct", | |
| "value": 0.02, | |
| "description": "Percentage-based padding applied to the x-domain on each side." | |
| }, | |
| { | |
| "name": "fullDomainRaw", | |
| "update": "length(data('dateStats')) ? [data('dateStats')[0].minStart, data('dateStats')[0].maxEnd] : [today, today]", | |
| "description": "Unpadded min-to-max date domain derived directly from the dataset." | |
| }, | |
| { | |
| "name": "fullSpanMs", | |
| "update": "max(toNumber(fullDomainRaw[1]) - toNumber(fullDomainRaw[0]), 1)", | |
| "description": "Duration of the unpadded full timeline domain in milliseconds." | |
| }, | |
| { | |
| "name": "axisPadMs", | |
| "update": "max(fullSpanMs * xDomainPadPct, 50*24*60*60*1000)", | |
| "description": "Computed time padding applied to the full x-domain on each side, with a minimum large enough to expose extrapolated leading and trailing axis labels." | |
| }, | |
| { | |
| "name": "ganttCanvasHeight", | |
| "update": "max(min(contentHeight, rowViewportHeight), 0)", | |
| "description": "Visible height of the Gantt drawing canvas after accounting for content height and viewport height." | |
| } | |
| ], | |
| "data": [ | |
| { | |
| "name": "dataset", | |
| "values": [ | |
| { | |
| "id": "mvp", | |
| "category": "Events", | |
| "rowOrder": 1, | |
| "lane": 0, | |
| "itemType": "milestone", | |
| "label": "MVP", | |
| "startDate": 1769904000000, | |
| "endDate": 1769904000000, | |
| "cadenceDays": null, | |
| "colorKey": "Events" | |
| }, | |
| { | |
| "id": "mvcr", | |
| "category": "Events", | |
| "rowOrder": 1, | |
| "lane": 1, | |
| "itemType": "milestone", | |
| "label": "MVCR", | |
| "startDate": 1811808000000, | |
| "endDate": 1811808000000, | |
| "cadenceDays": null, | |
| "colorKey": "Events" | |
| }, | |
| { | |
| "id": "atr-options", | |
| "category": "Contracts", | |
| "rowOrder": 2, | |
| "lane": 0, | |
| "itemType": "phase", | |
| "label": "ATR Performance Options", | |
| "startDate": 1759276800000, | |
| "endDate": 1811808000000, | |
| "cadenceDays": null, | |
| "colorKey": "Contracts" | |
| }, | |
| { | |
| "id": "atr-prod", | |
| "category": "Contracts", | |
| "rowOrder": 2, | |
| "lane": 1, | |
| "itemType": "phase", | |
| "label": "ATR Performance Production Contracts", | |
| "startDate": 1769904000000, | |
| "endDate": 1827619200000, | |
| "cadenceDays": null, | |
| "colorKey": "Contracts" | |
| }, | |
| { | |
| "id": "hw-dev", | |
| "category": "Contracts", | |
| "rowOrder": 2, | |
| "lane": 2, | |
| "itemType": "phase", | |
| "label": "HW Production Contract Development", | |
| "startDate": 1803859200000, | |
| "endDate": 1853971200000, | |
| "cadenceDays": null, | |
| "colorKey": "Contracts" | |
| }, | |
| { | |
| "id": "sprint-releases", | |
| "category": "Deliveries", | |
| "rowOrder": 3, | |
| "lane": 0, | |
| "itemType": "cadence", | |
| "label": "Sprint Releases", | |
| "startDate": 1759276800000, | |
| "endDate": 1948492800000, | |
| "cadenceDays": 45, | |
| "colorKey": "Deliveries" | |
| }, | |
| { | |
| "id": "system-testing", | |
| "category": "Testing & Evaluation", | |
| "rowOrder": 4, | |
| "lane": 0, | |
| "itemType": "phase", | |
| "label": "System Testing", | |
| "startDate": 1769904000000, | |
| "endDate": 1843430400000, | |
| "cadenceDays": null, | |
| "colorKey": "Testing & Evaluation" | |
| }, | |
| { | |
| "id": "mlops-dev", | |
| "category": "Development & Documentation", | |
| "rowOrder": 5, | |
| "lane": 0, | |
| "itemType": "phase", | |
| "label": "MLOps Development", | |
| "startDate": 1759276800000, | |
| "endDate": 1811808000000, | |
| "cadenceDays": null, | |
| "colorKey": "Development & Documentation" | |
| }, | |
| { | |
| "id": "hw-maturation", | |
| "category": "Development & Documentation", | |
| "rowOrder": 5, | |
| "lane": 1, | |
| "itemType": "phase", | |
| "label": "System HW Maturation", | |
| "startDate": 1759276800000, | |
| "endDate": 1827619200000, | |
| "cadenceDays": null, | |
| "colorKey": "Development & Documentation" | |
| }, | |
| { | |
| "id": "algo", | |
| "category": "Development & Documentation", | |
| "rowOrder": 5, | |
| "lane": 2, | |
| "itemType": "phase", | |
| "label": "Algorithm Improvements", | |
| "startDate": 1769904000000, | |
| "endDate": 1948492800000, | |
| "cadenceDays": null, | |
| "colorKey": "Development & Documentation" | |
| }, | |
| { | |
| "id": "data-collection", | |
| "category": "Development & Documentation", | |
| "rowOrder": 5, | |
| "lane": 3, | |
| "itemType": "phase", | |
| "label": "Data Collection", | |
| "startDate": 1759276800000, | |
| "endDate": 1948492800000, | |
| "cadenceDays": null, | |
| "colorKey": "Development & Documentation" | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "items", | |
| "source": "dataset", | |
| "transform": [ | |
| {"type": "formula", "as": "start", "expr": "toDate(datum.startDate)"}, | |
| {"type": "formula", "as": "end", "expr": "toDate(datum.endDate)"}, | |
| { | |
| "type": "formula", | |
| "as": "endSafe", | |
| "expr": "datum.end == null ? datum.start : datum.end" | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "dateStats", | |
| "source": "items", | |
| "transform": [ | |
| { | |
| "type": "aggregate", | |
| "fields": ["start", "endSafe"], | |
| "ops": ["min", "max"], | |
| "as": ["minStart", "maxEnd"] | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "categories", | |
| "source": "items", | |
| "transform": [ | |
| { | |
| "type": "aggregate", | |
| "groupby": ["category", "rowOrder", "colorKey"], | |
| "fields": ["lane", "id"], | |
| "ops": ["max", "count"], | |
| "as": ["maxLane", "itemCount"] | |
| }, | |
| {"type": "formula", "as": "laneCount", "expr": "datum.maxLane + 1"}, | |
| { | |
| "type": "formula", | |
| "as": "laneHeight", | |
| "expr": "max(minLaneHeight, datum.laneCount * nodeStep + laneTopPad + 8)" | |
| }, | |
| { | |
| "type": "collect", | |
| "sort": {"field": "rowOrder", "order": "ascending"} | |
| }, | |
| { | |
| "type": "window", | |
| "ops": ["sum"], | |
| "fields": ["laneHeight"], | |
| "as": ["y1"] | |
| }, | |
| {"type": "formula", "as": "y0", "expr": "datum.y1 - datum.laneHeight"}, | |
| { | |
| "type": "formula", | |
| "as": "yMid", | |
| "expr": "datum.y0 + datum.laneHeight/2" | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "contentStats", | |
| "source": "categories", | |
| "transform": [ | |
| { | |
| "type": "aggregate", | |
| "fields": ["y1"], | |
| "ops": ["max"], | |
| "as": ["contentHeight"] | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "itemLayout", | |
| "source": "items", | |
| "transform": [ | |
| { | |
| "type": "lookup", | |
| "from": "categories", | |
| "key": "category", | |
| "fields": ["category"], | |
| "values": ["y0", "laneHeight", "rowOrder", "colorKey"], | |
| "as": ["catY0", "catHeight", "catOrder", "catColor"] | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "y", | |
| "expr": "datum.catY0 + laneTopPad/2 + datum.lane * nodeStep + nodeStep/2" | |
| }, | |
| { | |
| "type": "collect", | |
| "sort": { | |
| "field": ["catOrder", "lane", "start"], | |
| "order": ["ascending", "ascending", "ascending"] | |
| } | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "phaseItems", | |
| "source": "itemLayout", | |
| "transform": [{"type": "filter", "expr": "datum.itemType === 'phase'"}] | |
| }, | |
| { | |
| "name": "milestoneItems", | |
| "source": "itemLayout", | |
| "transform": [ | |
| {"type": "filter", "expr": "datum.itemType === 'milestone'"} | |
| ] | |
| }, | |
| { | |
| "name": "cadenceMilestones", | |
| "source": "itemLayout", | |
| "transform": [ | |
| {"type": "filter", "expr": "datum.itemType === 'cadence'"}, | |
| { | |
| "type": "formula", | |
| "as": "dateMsArray", | |
| "expr": "sequence(toNumber(datum.start), toNumber(datum.endSafe)+((datum.cadenceDays) * 24 * 60 * 60 * 1000), (datum.cadenceDays) * 24 * 60 * 60 * 1000)" | |
| }, | |
| {"type": "flatten", "fields": ["dateMsArray"], "as": ["dateMs"]}, | |
| {"type": "formula", "as": "dateVal", "expr": "toDate(datum.dateMs)"} | |
| ] | |
| }, | |
| { | |
| "name": "cadenceItems", | |
| "source": "cadenceMilestones", | |
| "transform": [ | |
| { | |
| "type": "window", | |
| "ops": ["row_number"], | |
| "sort": {"field": ["dateVal"], "order": ["ascending"]}, | |
| "as": ["ascIndex"] | |
| }, | |
| { | |
| "type": "window", | |
| "ops": ["row_number"], | |
| "sort": {"field": ["dateVal"], "order": ["descending"]}, | |
| "as": ["descIndex"] | |
| }, | |
| { | |
| "type": "filter", | |
| "expr": "datum.ascIndex === 1 || datum.descIndex === 1" | |
| }, | |
| { | |
| "type": "aggregate", | |
| "ops": ["min", "max", "max"], | |
| "fields": ["dateVal", "dateVal", "dateVal"], | |
| "groupby": [ | |
| "category", | |
| "itemType", | |
| "label", | |
| "startDate", | |
| "endDate", | |
| "cadenceDays", | |
| "colorKey", | |
| "catY0", | |
| "catHeight", | |
| "catOrder", | |
| "catColor", | |
| "y", | |
| "dateMsArray" | |
| ], | |
| "as": ["start", "end", "endSafe"] | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "monthBands", | |
| "description": "Generated month bands derived from the padded full timeline domain.", | |
| "transform": [ | |
| { | |
| "type": "sequence", | |
| "start": 0, | |
| "stop": {"signal": "monthBandCount"}, | |
| "step": 1, | |
| "as": "monthIndex" | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "startDate", | |
| "expr": "utcOffset('month', monthBandStart, datum.monthIndex)" | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "endDate", | |
| "expr": "utcOffset('month', monthBandStart, datum.monthIndex + 1)" | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "midDate", | |
| "expr": "toDate((toNumber(datum.startDate) + toNumber(datum.endDate)) / 2)" | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "monthNum", | |
| "expr": "utcmonth(datum.startDate) + 1" | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "fiscalYear", | |
| "expr": "(datum.monthNum > fiscalYearStartMonth || (datum.monthNum === fiscalYearStartMonth && utcdate(datum.startDate) >= fiscalYearStartDay)) ? utcyear(datum.startDate) + 1 : utcyear(datum.startDate)" | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "fiscalQuarter", | |
| "expr": "floor(((datum.monthNum - fiscalYearStartMonth + 12) % 12) / 3) + 1" | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "monthLabel", | |
| "expr": "utcFormat(datum.startDate, '%b')" | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "quarterBands", | |
| "source": "monthBands", | |
| "transform": [ | |
| { | |
| "type": "aggregate", | |
| "groupby": ["fiscalYear", "fiscalQuarter"], | |
| "fields": ["startDate", "endDate"], | |
| "ops": ["min", "max"], | |
| "as": ["startDate", "endDate"] | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "midDate", | |
| "expr": "toDate((toNumber(datum.startDate)+toNumber(datum.endDate))/2)" | |
| }, | |
| {"type": "formula", "as": "label", "expr": "'Q' + datum.fiscalQuarter"}, | |
| { | |
| "type": "formula", | |
| "as": "fyQuarterLabel", | |
| "expr": "'FY' + slice(toString(datum.fiscalYear), -2) + ' Q' + datum.fiscalQuarter" | |
| }, | |
| { | |
| "type": "collect", | |
| "sort": {"field": "startDate", "order": "ascending"} | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "fyBands", | |
| "source": "monthBands", | |
| "transform": [ | |
| { | |
| "type": "aggregate", | |
| "groupby": ["fiscalYear"], | |
| "fields": ["startDate", "endDate"], | |
| "ops": ["min", "max"], | |
| "as": ["startDate", "endDate"] | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "midDate", | |
| "expr": "toDate((toNumber(datum.startDate)+toNumber(datum.endDate))/2)" | |
| }, | |
| { | |
| "type": "formula", | |
| "as": "label", | |
| "expr": "'FY' + substring('' + datum.fiscalYear, 2, 4)" | |
| }, | |
| { | |
| "type": "collect", | |
| "sort": {"field": "startDate", "order": "ascending"} | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "legendEntries", | |
| "values": [ | |
| {"order": 1, "kind": "phase", "label": "Phase with diamond endcaps"}, | |
| {"order": 2, "kind": "milestone", "label": "Milestone"}, | |
| {"order": 3, "kind": "cadence", "label": "Cadence series"}, | |
| {"order": 4, "kind": "today", "label": "Today line"} | |
| ] | |
| } | |
| ], | |
| "scales": [ | |
| { | |
| "name": "categoryColorScale", | |
| "type": "ordinal", | |
| "domain": {"signal": "categoryDomain"}, | |
| "range": {"signal": "categoryRange"} | |
| }, | |
| { | |
| "name": "xMain", | |
| "type": "utc", | |
| "domain": {"signal": "xDomain"}, | |
| "range": [0, {"signal": "mainTimelineWidth"}] | |
| }, | |
| { | |
| "name": "xOverview", | |
| "type": "utc", | |
| "domain": {"signal": "fullDomain"}, | |
| "range": [0, {"signal": "mainTimelineWidth"}] | |
| }, | |
| { | |
| "name": "yOverview", | |
| "type": "linear", | |
| "domain": {"signal": "overviewYDomain"}, | |
| "range": [0, {"signal": "miniMapInnerHeight"}] | |
| } | |
| ], | |
| "marks": [ | |
| { | |
| "name": "legend_group", | |
| "description": "Legend group explaining mark types, categories, and the today line.", | |
| "type": "group", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "width": {"signal": "width"}, | |
| "height": {"signal": "!showLegend ? 0 : legendHeight - 6"}, | |
| "clip": {"value": true} | |
| } | |
| }, | |
| "marks": [ | |
| { | |
| "name": "legend_title", | |
| "description": "Legend section title.", | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"value": 16}, | |
| "text": {"value": "Legend"}, | |
| "fontSize": {"value": 12}, | |
| "fontWeight": {"value": "bold"}, | |
| "fill": {"value": "#374151"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_phase_rule_sample", | |
| "description": "Legend sample rule for phase duration bars.", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 78}, | |
| "x2": {"value": 130}, | |
| "y": {"value": 15, "offset": -2.5}, | |
| "y2": {"value": 15, "offset": 2.5}, | |
| "stroke": {"value": "#777"}, | |
| "strokeWidth": {"value": 0.6} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_phase_start_symbol", | |
| "description": "Legend sample diamond for a phase start cap.", | |
| "type": "symbol", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 78}, | |
| "y": {"value": 15}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"value": 70}, | |
| "strokeWidth": {"value": 0.6}, | |
| "fill": {"value": "background"}, | |
| "stroke": {"value": "#777"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_phase_end_symbol", | |
| "description": "Legend sample diamond for a phase end cap.", | |
| "type": "symbol", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 130}, | |
| "y": {"value": 15}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"value": 70}, | |
| "strokeWidth": {"value": 0.6}, | |
| "fill": {"value": "background"}, | |
| "stroke": {"value": "#777"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_phase_label", | |
| "description": "Legend label for the phase mark type.", | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 142}, | |
| "y": {"value": 15}, | |
| "text": {"value": "Phase with diamond endcaps"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 11}, | |
| "fill": {"value": "#111827"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_milestone_symbol", | |
| "description": "Legend sample diamond for milestone items.", | |
| "type": "symbol", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 340}, | |
| "y": {"value": 15}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"value": 80}, | |
| "fill": {"value": "background"}, | |
| "stroke": {"value": "#777"}, | |
| "strokeWidth": {"value": 0.6} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_milestone_label", | |
| "description": "Legend label for milestone items.", | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 352}, | |
| "y": {"value": 15}, | |
| "text": {"value": "Milestone"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 11}, | |
| "fill": {"value": "#111827"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_cadence_rule_sample", | |
| "description": "Legend sample dashed rule for cadence items.", | |
| "type": "rule", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 455}, | |
| "x2": {"value": 510}, | |
| "y": {"value": 15}, | |
| "stroke": {"value": "#777"}, | |
| "strokeWidth": {"value": 1.5}, | |
| "strokeDash": {"value": [3, 2]} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_cadence_symbol", | |
| "description": "Legend sample diamond for cadence milestones.", | |
| "type": "symbol", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 482}, | |
| "y": {"value": 15}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"value": 62}, | |
| "fill": {"signal": "background"}, | |
| "stroke": {"value": "#777"}, | |
| "strokeWidth": {"value": 0.6} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_cadence_label", | |
| "description": "Legend label for cadence items.", | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 522}, | |
| "y": {"value": 15}, | |
| "text": {"value": "Cadence series"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 11}, | |
| "fill": {"value": "#111827"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_today_rule_sample", | |
| "description": "Legend sample rule for the today reference line.", | |
| "type": "rule", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 650}, | |
| "x2": {"value": 650}, | |
| "y": {"value": 4}, | |
| "y2": {"value": 28}, | |
| "stroke": {"value": "#2563EB"}, | |
| "strokeWidth": {"value": 1.5}, | |
| "strokeDash": {"value": [4, 3]} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "legend_today_label", | |
| "description": "Legend label for the today reference line.", | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 662}, | |
| "y": {"value": 15}, | |
| "text": {"value": "Today line"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 11}, | |
| "fill": {"value": "#111827"} | |
| } | |
| } | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "chart_group", | |
| "type": "group", | |
| "encode": { | |
| "update": {"y": {"signal": "data('legend_group')[0].bounds.y2"}} | |
| }, | |
| "marks": [ | |
| { | |
| "name": "header_workstream_background", | |
| "description": "Background for the fixed Workstream header cell.", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "leftLabelWidth"}, | |
| "height": {"signal": "headerHeight"}, | |
| "fill": {"value": "#003366"}, | |
| "stroke": {"value": "#111827"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_timeline_background", | |
| "description": "Background for the sticky timeline header area.", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth"}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "mainTimelineWidth"}, | |
| "height": {"signal": "headerHeight"}, | |
| "fill": {"value": "#003366"}, | |
| "stroke": {"value": "#111827"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_workstream_title", | |
| "description": "Title text displayed in the fixed Workstream header cell.", | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth/2"}, | |
| "y": {"signal": "headerHeight/2"}, | |
| "text": {"value": "Workstream"}, | |
| "align": {"value": "center"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 18}, | |
| "fontWeight": {"value": "bold"}, | |
| "fill": {"value": "#FFFFFF"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_first_vertical_divider", | |
| "description": "Vertical divider separating the workstream header and the timeline.", | |
| "type": "rule", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth", "offset": 0.06}, | |
| "y": {"value": 0}, | |
| "y2": {"signal": "headerHeight"}, | |
| "stroke": {"value": "#E5E7EB"}, | |
| "strokeWidth": {"value": 1.2} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_axis_group", | |
| "description": "Sticky group that renders the upper and lower timeline axis tiers.", | |
| "type": "group", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth"}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "mainTimelineWidth"}, | |
| "height": {"signal": "headerHeight"}, | |
| "clip": {"value": true} | |
| } | |
| }, | |
| "marks": [ | |
| { | |
| "name": "header_axis_border", | |
| "description": "Border rectangle for the sticky timeline header.", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "mainTimelineWidth"}, | |
| "height": {"signal": "headerHeight"}, | |
| "fill": {"value": "#003366"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_fiscal_year_dividers", | |
| "description": "Vertical dividers separating fiscal year bands in the header.", | |
| "type": "rule", | |
| "from": {"data": "fyBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "startDate"}, | |
| "y": {"value": 0}, | |
| "y2": {"signal": "headerHeight"}, | |
| "stroke": {"value": "#E5E7EB"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_month_dividers", | |
| "description": "Vertical dividers separating each month.", | |
| "type": "rule", | |
| "from": {"data": "monthBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "startDate"}, | |
| "y": {"signal": "headerHeight/2"}, | |
| "y2": {"signal": "headerHeight"}, | |
| "stroke": {"value": "#E5E7EB"}, | |
| "strokeWidth": {"value": 0.75}, | |
| "opacity": { | |
| "signal": "zoomMode === 'fy_quarter_month' ? 1 : 0" | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_fiscal_year_quarter_dividers", | |
| "description": "Vertical dividers separating fiscal year + quarter bands in the header.", | |
| "type": "rule", | |
| "from": {"data": "quarterBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "startDate"}, | |
| "y": {"value": 0}, | |
| "y2": {"signal": "headerHeight"}, | |
| "stroke": {"value": "#E5E7EB"}, | |
| "strokeWidth": {"value": 1.2}, | |
| "opacity": { | |
| "signal": "zoomMode === 'fy_quarter_month' ? 1 : 0" | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_quarter_dividers", | |
| "description": "Vertical dividers separating quarter bands in the header.", | |
| "type": "rule", | |
| "from": {"data": "quarterBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "startDate"}, | |
| "y": {"signal": "headerHeight/2"}, | |
| "y2": {"signal": "headerHeight"}, | |
| "stroke": {"value": "#E5E7EB"}, | |
| "strokeWidth": {"value": 0.75} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_axis_bottom_rule", | |
| "description": "Bottom border of the sticky timeline header.", | |
| "type": "rule", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "x2": {"signal": "mainTimelineWidth"}, | |
| "y": {"signal": "headerHeight/2"}, | |
| "stroke": {"value": "#E5E7EB"}, | |
| "strokeWidth": {"value": 1.2} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_fiscal_year_labels", | |
| "description": "Upper-tier fiscal year labels shown in FY plus quarter mode.", | |
| "type": "text", | |
| "from": {"data": "fyBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "midDate"}, | |
| "y": {"signal": "headerHeight*0.28"}, | |
| "text": {"field": "label"}, | |
| "align": {"value": "center"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 17}, | |
| "fontWeight": {"value": "bold"}, | |
| "fill": {"value": "#FFFFFF"}, | |
| "opacity": { | |
| "signal": "zoomMode === 'fy_quarter' && (scale('xMain', datum.endDate) - scale('xMain', datum.startDate) >= 64) ? 1 : 0" | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_fiscal_quarter_labels", | |
| "description": "Upper-tier fiscal quarter labels shown in FY plus quarter plus month mode.", | |
| "type": "text", | |
| "from": {"data": "quarterBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "midDate"}, | |
| "y": {"signal": "headerHeight*0.28"}, | |
| "text": {"field": "fyQuarterLabel"}, | |
| "align": {"value": "center"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 15}, | |
| "fontWeight": {"value": "bold"}, | |
| "fill": {"value": "#FFFFFF"}, | |
| "opacity": { | |
| "signal": "zoomMode === 'fy_quarter_month' && (scale('xMain', datum.endDate) - scale('xMain', datum.startDate) >= 68) ? 1 : 0" | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| "name": "header_quarter_labels", | |
| "description": "Lower-tier quarter labels shown in FY plus quarter mode.", | |
| "type": "text", | |
| "from": {"data": "quarterBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "midDate"}, | |
| "y": {"signal": "headerHeight*0.76"}, | |
| "text": {"field": "label"}, | |
| "align": {"value": "center"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 13}, | |
| "fill": {"value": "#FFFFFF"}, | |
| "opacity": { | |
| "signal": "zoomMode === 'fy_quarter' && (scale('xMain', datum.endDate) - scale('xMain', datum.startDate) >= 42) ? 1 : 0" | |
| } | |
| } | |
| } | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "main_body_group", | |
| "description": "Outer group for the main Gantt body, including labels, rows, and the vertical scrollbar.", | |
| "type": "group", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"signal": "mainChartTop"}, | |
| "width": {"signal": "width"}, | |
| "height": {"signal": "ganttCanvasHeight"}, | |
| "clip": {"value": true} | |
| } | |
| }, | |
| "marks": [ | |
| { | |
| "name": "left_label_pane", | |
| "description": "Fixed left pane that renders swim-lane labels and separators.", | |
| "type": "group", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "leftLabelWidth"}, | |
| "height": {"signal": "ganttCanvasHeight"}, | |
| "clip": {"value": true} | |
| } | |
| }, | |
| "marks": [ | |
| { | |
| "name": "left_label_pane_background", | |
| "description": "Background and border rectangle for the fixed left label pane.", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "leftLabelWidth"}, | |
| "height": {"signal": "ganttCanvasHeight"}, | |
| "fill": {"value": "#FFFFFF"}, | |
| "stroke": {"value": "#111827"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| } | |
| }, | |
| { | |
| "type": "rect", | |
| "from": {"data": "categories"}, | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"signal": "datum.y0 - yScroll"}, | |
| "width": {"signal": "leftLabelWidth"}, | |
| "height": {"field": "laneHeight"}, | |
| "fill": {"value": "#FFFFFF"}, | |
| "stroke": {"value": "#111827"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| }, | |
| "name": "left_lane_backgrounds", | |
| "description": "Per-category background rectangles for each fixed swim-lane label area." | |
| }, | |
| { | |
| "name": "left_lane_labels", | |
| "description": "Category labels displayed inside the fixed left pane.", | |
| "type": "text", | |
| "from": {"data": "categories"}, | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth/2"}, | |
| "y": {"signal": "datum.yMid - yScroll"}, | |
| "text": {"field": "category"}, | |
| "align": {"value": "center"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 14}, | |
| "fontWeight": {"value": "bold"}, | |
| "fill": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| } | |
| } | |
| } | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "rows_viewport_group", | |
| "description": "Clipped viewport that renders the scrollable Gantt marks and gridlines.", | |
| "type": "group", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth"}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "mainTimelineWidth"}, | |
| "height": {"signal": "ganttCanvasHeight"}, | |
| "clip": {"value": true} | |
| } | |
| }, | |
| "marks": [ | |
| { | |
| "name": "rows_viewport_background", | |
| "description": "Background rectangle for the clipped Gantt viewport.", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "mainTimelineWidth"}, | |
| "height": {"signal": "ganttCanvasHeight"}, | |
| "fill": {"signal": "background"}, | |
| "stroke": {"value": "#111827"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "rows_lane_backgrounds", | |
| "description": "Per-category background rectangles for each visible swim lane.", | |
| "type": "rect", | |
| "from": {"data": "categories"}, | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"signal": "datum.y0 - yScroll"}, | |
| "width": {"signal": "mainTimelineWidth"}, | |
| "height": {"field": "laneHeight"}, | |
| "fill": {"signal": "background"}, | |
| "stroke": {"value": "#111827"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "rows_quarter_gridlines", | |
| "description": "Quarter-level gridlines drawn through the Gantt body.", | |
| "type": "rule", | |
| "from": {"data": "quarterBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "startDate"}, | |
| "y": {"value": 0}, | |
| "y2": { | |
| "signal": "max(min(contentHeight - yScroll, ganttCanvasHeight),0)" | |
| }, | |
| "stroke": {"value": "#E5E7EB"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "rows_month_gridlines", | |
| "description": "Month-level gridlines drawn through the Gantt body at finer zoom.", | |
| "type": "rule", | |
| "from": {"data": "monthBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "startDate"}, | |
| "y": {"value": 0}, | |
| "y2": { | |
| "signal": "max(min(contentHeight - yScroll, ganttCanvasHeight),0)" | |
| }, | |
| "stroke": {"value": "#F3F4F6"}, | |
| "strokeWidth": {"value": 1}, | |
| "opacity": { | |
| "signal": "zoomMode === 'fy_quarter_month' ? 1 : 0" | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| "name": "rows_today_line", | |
| "description": "Today reference line drawn through the Gantt body when enabled.", | |
| "type": "rule", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "scale('xMain', today)"}, | |
| "y": {"value": 0}, | |
| "y2": { | |
| "signal": "max(min(contentHeight - yScroll, ganttCanvasHeight),0)" | |
| }, | |
| "stroke": {"value": "#2563EB"}, | |
| "strokeWidth": {"value": 1.5}, | |
| "strokeDash": {"value": [4, 3]}, | |
| "opacity": {"signal": "showTodayLine ? 1 : 0"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "phase_duration_rects_background", | |
| "description": "Duration lines for phase items.", | |
| "type": "rect", | |
| "from": {"data": "phaseItems"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "start"}, | |
| "x2": {"scale": "xMain", "field": "endSafe"}, | |
| "y": { | |
| "signal": "datum.y - yScroll", | |
| "offset": {"signal": "-barHeight/2"} | |
| }, | |
| "y2": { | |
| "signal": "datum.y - yScroll", | |
| "offset": {"signal": "barHeight/2"} | |
| }, | |
| "stroke": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| }, | |
| "strokeWidth": {"signal": "1"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "phase_duration_rects", | |
| "description": "Duration lines for phase items.", | |
| "type": "rect", | |
| "from": {"data": "phaseItems"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "start"}, | |
| "x2": { | |
| "signal": "datum.start < today ? min(scale('xMain', datum.endSafe), scale('xMain', today)) : scale('xMain', datum.start)" | |
| }, | |
| "y": { | |
| "signal": "datum.y - yScroll", | |
| "offset": {"signal": "-barHeight/2"} | |
| }, | |
| "y2": { | |
| "signal": "datum.y - yScroll", | |
| "offset": {"signal": "barHeight/2"} | |
| }, | |
| "fill": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| "name": "phase_start_diamonds_background", | |
| "description": "Diamond start caps for phase items.", | |
| "type": "symbol", | |
| "from": {"data": "phaseItems"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "start"}, | |
| "y": {"signal": "datum.y - yScroll"}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"signal": "milestoneSize"}, | |
| "fill": {"signal": "background"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "phase_start_diamonds", | |
| "description": "Diamond start caps for phase items.", | |
| "type": "symbol", | |
| "from": {"data": "phaseItems"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "start"}, | |
| "y": {"signal": "datum.y - yScroll"}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"signal": "milestoneSize"}, | |
| "fill": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| }, | |
| "strokeWidth": {"value": 0.6}, | |
| "stroke": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| }, | |
| "fillOpacity": {"signal": "datum.start > today ? 0 : 1"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "phase_end_diamonds_background", | |
| "description": "Diamond end cap background for phase items.", | |
| "type": "symbol", | |
| "from": {"data": "phaseItems"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "endSafe"}, | |
| "y": {"signal": "datum.y - yScroll"}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"signal": "milestoneSize"}, | |
| "fill": {"signal": "background"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "phase_end_diamonds", | |
| "description": "Diamond end caps for phase items.", | |
| "type": "symbol", | |
| "from": {"data": "phaseItems"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "endSafe"}, | |
| "y": {"signal": "datum.y - yScroll"}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"signal": "milestoneSize"}, | |
| "fill": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| }, | |
| "stroke": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| }, | |
| "strokeWidth": {"value": 0.6}, | |
| "fillOpacity": {"signal": "datum.endSafe < today ? 1 : 0"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "phase_label_backgrounds", | |
| "description": "Labels for phase items placed above the duration lines.", | |
| "type": "text", | |
| "from": {"data": "phaseItems"}, | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "scale": "xMain", | |
| "signal": "max(datum.start, xDomain[0])", | |
| "offset": { | |
| "signal": "xDomain[0] >= datum.start ? 7.5 : 0" | |
| } | |
| }, | |
| "y": {"signal": "datum.y - yScroll - labelDy"}, | |
| "text": { | |
| "signal": "inrange(datum.start, xDomain) || inrange(datum.endSafe, xDomain) || (datum.start < xDomain[0] && datum.endSafe > xDomain[1]) ? datum.label : null" | |
| }, | |
| "align": {"value": "left"}, | |
| "baseline": {"value": "bottom"}, | |
| "dx": {"value": -4}, | |
| "fontSize": {"value": 11}, | |
| "stroke": {"signal": "background"}, | |
| "strokeWidth": {"value": 3}, | |
| "fill": {"value": "background"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "phase_labels", | |
| "description": "Labels for phase items placed above the duration lines.", | |
| "type": "text", | |
| "from": {"data": "phaseItems"}, | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "scale": "xMain", | |
| "signal": "max(datum.start, xDomain[0])", | |
| "offset": { | |
| "signal": "xDomain[0] >= datum.start ? 7.5 : 0" | |
| } | |
| }, | |
| "y": {"signal": "datum.y - yScroll - labelDy"}, | |
| "text": { | |
| "signal": "inrange(datum.start, xDomain) || inrange(datum.endSafe, xDomain) || (datum.start < xDomain[0] && datum.endSafe > xDomain[1]) ? datum.label : null" | |
| }, | |
| "align": {"value": "left"}, | |
| "baseline": {"value": "bottom"}, | |
| "dx": {"value": -4}, | |
| "fontSize": {"value": 11}, | |
| "fill": {"value": "#111827"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "cadence_duration_rules", | |
| "description": "Dashed cadence guide lines connecting repeating cadence milestones.", | |
| "type": "rule", | |
| "from": {"data": "cadenceItems"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "start"}, | |
| "x2": {"scale": "xMain", "field": "endSafe"}, | |
| "y": {"signal": "datum.y - yScroll"}, | |
| "stroke": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| }, | |
| "strokeWidth": {"value": 1.2}, | |
| "strokeDash": {"value": [2, 3]} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "cadence_milestone_diamonds_background", | |
| "description": "Diamond symbol backgrounds for generated cadence milestones.", | |
| "type": "symbol", | |
| "from": {"data": "cadenceMilestones"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "dateVal"}, | |
| "y": {"signal": "datum.y - yScroll"}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"signal": "cadenceSymbolSize"}, | |
| "fill": {"value": "background"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "cadence_milestone_diamonds", | |
| "description": "Diamond symbols for generated cadence milestones.", | |
| "type": "symbol", | |
| "from": {"data": "cadenceMilestones"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "dateVal"}, | |
| "y": {"signal": "datum.y - yScroll"}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"signal": "cadenceSymbolSize"}, | |
| "fill": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| }, | |
| "stroke": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| }, | |
| "fillOpacity": {"signal": "datum.dateVal > today ? 0 : 1"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "cadence_label_backgrounds", | |
| "description": "Single label backgrounds for cadence rows placed above the cadence line.", | |
| "type": "text", | |
| "from": {"data": "cadenceItems"}, | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "scale": "xMain", | |
| "signal": "max(datum.start, xDomain[0])", | |
| "offset": { | |
| "signal": "xDomain[0] >= datum.start ? 7.5 : 0" | |
| } | |
| }, | |
| "y": {"signal": "datum.y - yScroll - labelDy"}, | |
| "text": { | |
| "signal": "inrange(datum.start, xDomain) || inrange(datum.endSafe, xDomain) || (datum.start < xDomain[0] && datum.endSafe > xDomain[1]) ? datum.label : null" | |
| }, | |
| "align": {"value": "left"}, | |
| "baseline": {"value": "bottom"}, | |
| "fontSize": {"value": 11}, | |
| "strokeWidth": {"value": 3}, | |
| "fill": {"value": "background"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "cadence_labels", | |
| "description": "Single labels for cadence rows placed above the cadence line.", | |
| "type": "text", | |
| "from": {"data": "cadenceItems"}, | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "scale": "xMain", | |
| "signal": "max(datum.start, xDomain[0])", | |
| "offset": { | |
| "signal": "xDomain[0] >= datum.start ? 3.5 : -7.5" | |
| } | |
| }, | |
| "y": {"signal": "datum.y - yScroll - labelDy"}, | |
| "text": { | |
| "signal": "inrange(datum.start, xDomain) || inrange(datum.endSafe, xDomain) || (datum.start < xDomain[0] && datum.endSafe > xDomain[1]) ? datum.label : null" | |
| }, | |
| "align": {"value": "left"}, | |
| "baseline": {"value": "bottom"}, | |
| "fontSize": {"value": 11}, | |
| "fill": {"value": "#111827"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "milestone_diamonds", | |
| "description": "Diamond symbols for standalone milestone items.", | |
| "type": "symbol", | |
| "from": {"data": "milestoneItems"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "start"}, | |
| "y": {"signal": "datum.y - yScroll"}, | |
| "shape": {"value": "diamond"}, | |
| "size": {"signal": "milestoneSize"}, | |
| "fill": { | |
| "scale": "categoryColorScale", | |
| "field": "category" | |
| }, | |
| "stroke": {"value": "#111827"}, | |
| "strokeWidth": {"value": 0.7} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "milestone_labels", | |
| "description": "Labels for standalone milestones placed above the symbols.", | |
| "type": "text", | |
| "from": {"data": "milestoneItems"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "start"}, | |
| "y": {"signal": "datum.y - yScroll - labelDy"}, | |
| "text": {"field": "label"}, | |
| "align": {"value": "center"}, | |
| "baseline": {"value": "bottom"}, | |
| "fontSize": {"value": 11}, | |
| "fontWeight": {"value": "bold"}, | |
| "fill": {"value": "#111827"} | |
| } | |
| } | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "chart_viewport_bottom_stroke", | |
| "description": "Stroke rectangle for the clipped Gantt viewport.", | |
| "type": "rule", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"signal": "ganttCanvasHeight"}, | |
| "x2": {"signal": "leftLabelWidth+mainTimelineWidth"}, | |
| "stroke": {"value": "#111827"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "vertical_scrollbar_group", | |
| "description": "Group containing the custom vertical scrollbar track and handle.", | |
| "type": "group", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth + mainTimelineWidth + 1"}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "scrollbarWidth"}, | |
| "height": {"signal": "ganttCanvasHeight"}, | |
| "opacity": {"signal": "maxYScroll > 0 ? 1 : 0"}, | |
| "clip": {"value": true} | |
| } | |
| }, | |
| "marks": [ | |
| { | |
| "name": "vertical_scrollbar_track", | |
| "description": "Track for the custom vertical scrollbar.", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "scrollbarWidth"}, | |
| "height": {"signal": "scrollTrackHeight"}, | |
| "cornerRadius": {"value": 6}, | |
| "fill": {"value": "#F3F4F6"}, | |
| "stroke": {"value": "#D1D5DB"}, | |
| "strokeWidth": {"value": 1}, | |
| "cursor": {"value": "pointer"} | |
| } | |
| } | |
| }, | |
| { | |
| "name": "vertical_scrollbar_handle", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 1}, | |
| "y": { | |
| "signal": "maxYScroll > 0 ? (yScroll / maxYScroll) * (scrollTrackHeight - scrollHandleHeight) : 0" | |
| }, | |
| "width": {"signal": "scrollbarWidth - 2"}, | |
| "height": {"signal": "scrollHandleHeight"}, | |
| "cornerRadius": {"value": 6}, | |
| "fill": {"value": "#9CA3AF"}, | |
| "cursor": {"value": "grab"} | |
| } | |
| }, | |
| "description": "Draggable handle for the custom vertical scrollbar." | |
| } | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "header_month_label_group", | |
| "description": "Clipped group that renders lower-tier month labels at the finest zoom level.", | |
| "type": "group", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth"}, | |
| "y": {"value": 0}, | |
| "width": {"signal": "mainTimelineWidth"}, | |
| "height": {"signal": "headerHeight"}, | |
| "clip": {"value": true} | |
| } | |
| }, | |
| "marks": [ | |
| { | |
| "name": "header_month_labels", | |
| "description": "Lower-tier month labels shown in FY plus quarter plus month mode.", | |
| "type": "text", | |
| "from": {"data": "monthBands"}, | |
| "encode": { | |
| "update": { | |
| "x": {"scale": "xMain", "field": "midDate"}, | |
| "y": {"signal": "headerHeight*0.76"}, | |
| "text": {"field": "monthLabel"}, | |
| "align": {"value": "center"}, | |
| "baseline": {"value": "middle"}, | |
| "fontSize": {"value": 12}, | |
| "fill": {"value": "#FFFFFF"}, | |
| "opacity": { | |
| "signal": "zoomMode === 'fy_quarter_month' && (scale('xMain', datum.endDate) - scale('xMain', datum.startDate) >= 32) ? 1 : 0" | |
| } | |
| } | |
| } | |
| } | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "minimap_group", | |
| "description": "Overview mini-map group used for timeline brushing and reset.", | |
| "type": "group", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 0}, | |
| "y": {"signal": "data('chart_group')[0].bounds.y2+14"}, | |
| "width": {"signal": "width"}, | |
| "height": {"signal": "!showMiniMap ? 0 : miniMapHeight"}, | |
| "clip": {"value": true} | |
| } | |
| }, | |
| "marks": [ | |
| { | |
| "name": "minimap_background", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth"}, | |
| "y": {"signal": "miniMapInnerPadTop"}, | |
| "width": {"signal": "mainTimelineWidth"}, | |
| "height": {"signal": "miniMapInnerHeight"}, | |
| "fill": {"value": "#F9FAFB"}, | |
| "stroke": {"value": "#D1D5DB"}, | |
| "strokeWidth": {"value": 1}, | |
| "cursor": {"value": "pointer"} | |
| } | |
| }, | |
| "description": "Background rectangle for the overview mini-map drawing area." | |
| }, | |
| { | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth"}, | |
| "y": {"signal": "miniMapLabelPadTop"}, | |
| "text": {"value": "Overview"}, | |
| "align": {"value": "left"}, | |
| "baseline": {"value": "top"}, | |
| "fontSize": {"value": 12}, | |
| "fontWeight": {"value": "bold"}, | |
| "fill": {"value": "#374151"} | |
| } | |
| }, | |
| "name": "minimap_title", | |
| "description": "Title text displayed above the mini-map." | |
| }, | |
| { | |
| "name": "minimap_reset_button", | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth + mainTimelineWidth"}, | |
| "y": {"signal": "miniMapLabelPadTop"}, | |
| "text": {"value": "Reset View"}, | |
| "align": {"value": "right"}, | |
| "baseline": {"value": "top"}, | |
| "fontSize": {"value": 12}, | |
| "fill": {"value": "#2563EB"}, | |
| "cursor": {"value": "pointer"} | |
| } | |
| }, | |
| "description": "Reset control displayed above the mini-map." | |
| }, | |
| { | |
| "type": "rect", | |
| "from": {"data": "categories"}, | |
| "encode": { | |
| "update": { | |
| "x": {"signal": "leftLabelWidth"}, | |
| "y": { | |
| "signal": "miniMapInnerPadTop + scale('yOverview', datum.y0)" | |
| }, | |
| "width": {"signal": "mainTimelineWidth"}, | |
| "height": { | |
| "signal": "max(scale('yOverview', datum.laneHeight) - scale('yOverview', 0), 1)" | |
| }, | |
| "fill": {"value": "#FFFFFF"}, | |
| "stroke": {"value": "#E5E7EB"}, | |
| "strokeWidth": {"value": 1} | |
| } | |
| }, | |
| "name": "minimap_lane_backgrounds", | |
| "description": "Per-category background rectangles in the overview mini-map." | |
| }, | |
| { | |
| "type": "rule", | |
| "from": {"data": "phaseItems"}, | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "signal": "leftLabelWidth + scale('xOverview', datum.start)" | |
| }, | |
| "x2": { | |
| "signal": "leftLabelWidth + scale('xOverview', datum.endSafe)" | |
| }, | |
| "y": { | |
| "signal": "miniMapInnerPadTop + scale('yOverview', datum.y)" | |
| }, | |
| "stroke": {"scale": "categoryColorScale", "field": "category"}, | |
| "strokeWidth": {"value": 2} | |
| } | |
| }, | |
| "name": "minimap_phase_rules", | |
| "description": "Overview duration lines for phase items." | |
| }, | |
| { | |
| "type": "rule", | |
| "from": {"data": "cadenceItems"}, | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "signal": "leftLabelWidth + scale('xOverview', datum.start)" | |
| }, | |
| "x2": { | |
| "signal": "leftLabelWidth + scale('xOverview', datum.endSafe)" | |
| }, | |
| "y": { | |
| "signal": "miniMapInnerPadTop + scale('yOverview', datum.y)" | |
| }, | |
| "stroke": {"scale": "categoryColorScale", "field": "category"}, | |
| "strokeWidth": {"value": 1.2}, | |
| "strokeDash": {"value": [3, 2]} | |
| } | |
| }, | |
| "name": "minimap_cadence_rules", | |
| "description": "Overview cadence lines for cadence items." | |
| }, | |
| { | |
| "type": "symbol", | |
| "from": {"data": "milestoneItems"}, | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "signal": "leftLabelWidth + scale('xOverview', datum.start)" | |
| }, | |
| "y": { | |
| "signal": "miniMapInnerPadTop + scale('yOverview', datum.y)" | |
| }, | |
| "shape": {"value": "diamond"}, | |
| "size": {"value": 25}, | |
| "fill": {"scale": "categoryColorScale", "field": "category"}, | |
| "stroke": {"value": "#111827"}, | |
| "strokeWidth": {"value": 0.4} | |
| } | |
| }, | |
| "name": "minimap_milestones", | |
| "description": "Overview milestone symbols shown in the mini-map." | |
| }, | |
| { | |
| "name": "minimap_window", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "signal": "leftLabelWidth + brushStartPct * mainTimelineWidth" | |
| }, | |
| "y": {"signal": "miniMapInnerPadTop"}, | |
| "width": { | |
| "signal": "(brushEndPct - brushStartPct) * mainTimelineWidth" | |
| }, | |
| "height": {"signal": "miniMapInnerHeight"}, | |
| "fill": {"value": "#BFDBFE"}, | |
| "fillOpacity": {"value": 0.28}, | |
| "stroke": {"value": "#2563EB"}, | |
| "strokeWidth": {"value": 1.5}, | |
| "cursor": {"value": "grab"} | |
| } | |
| }, | |
| "description": "Current overview selection window that controls the visible x-domain." | |
| }, | |
| { | |
| "name": "minimap_left_handle", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "signal": "leftLabelWidth + brushStartPct * mainTimelineWidth - 4" | |
| }, | |
| "y": {"signal": "miniMapInnerPadTop - 1"}, | |
| "width": {"value": 8}, | |
| "height": {"signal": "miniMapInnerHeight + 2"}, | |
| "fill": {"value": "#2563EB"}, | |
| "cursor": {"value": "ew-resize"} | |
| } | |
| }, | |
| "description": "Left drag handle for resizing the overview selection window." | |
| }, | |
| { | |
| "name": "minimap_right_handle", | |
| "type": "rect", | |
| "encode": { | |
| "update": { | |
| "x": { | |
| "signal": "leftLabelWidth + brushEndPct * mainTimelineWidth - 4" | |
| }, | |
| "y": {"signal": "miniMapInnerPadTop - 1"}, | |
| "width": {"value": 8}, | |
| "height": {"signal": "miniMapInnerHeight + 2"}, | |
| "fill": {"value": "#2563EB"}, | |
| "cursor": {"value": "ew-resize"} | |
| } | |
| }, | |
| "description": "Right drag handle for resizing the overview selection window." | |
| } | |
| ] | |
| } | |
| ] | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment