Skip to content

Instantly share code, notes, and snippets.

@Giammaria
Last active April 21, 2026 13:50
Show Gist options
  • Select an option

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

Select an option

Save Giammaria/f966835fbeed4e31581fb570ac98d786 to your computer and use it in GitHub Desktop.
20260420_RAD_Schedule
{
"$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