如何在Deneb甘特图中实现按经办人分组的层级Y轴?
问题描述
我正在制作Deneb甘特图,想要实现按经办人(Assignee)分组展示项目的效果,类似Power BI表格里把经办人和项目堆叠在行上,支持点击展开/折叠查看经办人所有项目的形式。目前我把经办人放在Y轴,任务用范围条形图展示,但如果不在Y轴列出项目(比如经办人1同时负责Project1和Project2的任务),图表会变得十分混乱。我尝试用分面(facet)但没效果,想知道甘特图是否支持分面,或者该怎么实现这个需求。
示例数据
| Project | Task | Assignee | StartDate | EndDate |
|---|---|---|---|---|
| Project1 | Task1 | Assignee1 | 01/05/2023 | 30/06/2023 |
| Project1 | Task1 | Assignee2 | 01/04/2023 | 31/05/2023 |
| Project1 | Task2 | Assignee3 | 01/07/2023 | 14/07/2023 |
| Project2 | Task1 | Assignee1 | 01/07/2023 | 31/07/2023 |
示例代码
{ "title": { "text": "Title", "fontSize": 16, "color": "#34282C" }, "data": {"name": "dataset"}, "layer": [ { "transform": [ { "calculate": "now()", "as": "Today" } ], "mark": { "type": "rule", "color": "Sienna", "strokeDash": [0, 0], "size": 1, "tooltip": true }, "encoding": { "x": { "field": "Today", "type": "temporal" } } }, { "layer": [ { "params": [ { "name": "grid", "select": "interval", "bind": "scales" }, { "name": "highlight", "select": { "type": "point", "on": "mouseover" } }, { "name": "select", "select": "point" } ], "mark": { "type": "bar", "opacity": 1, "cornerRadius": 6, "height": 25, "tooltip": true, "color": "#FFFFFF" } }, { "mark": { "type": "bar", "opacity": 0.6, "cornerRadius": 6, "height": 20, "tooltip": true, "stroke": "black" }, "encoding": { "color": { "field": "Assignee", "legend": null } } }, { "mark": { "type": "text", "align": "left", "dx": 5, "fontSize": 12 }, "encoding": { "text": { "field": "Task", "type": "nominal" } } } ], "encoding": { "y": { "field": "Assignee", "type": "ordinal", "axis": { "title": null, "grid": true, "gridWidth": 2, "tickBand": "extent", "labelFontSize": 15, "labelColor": "#34282C", "zindex": 1 } }, "x": { "field": "StartDate", "type": "temporal", "axis": { "title": null, "format": "%m/%y", "labelExpr": "[timeFormat(datum.value, '%m/%y'), timeFormat(datum.value, '%m') == '01' ? timeFormat(datum.value, '%Y') : '']", "orient": "top", "grid": true, "gridWidth": 2, "gridColor": "#ddd", "gridDash": { "condition": { "test": { "field": "value", "timeUnit": "month", "equal": 1 }, "value": [] }, "value": [2, 2] }, "labelOffset": 15, "labelAlign": "left", "labelPadding": 0 } }, "x2": {"field": "EndDate"}, "yOffset": { "field": "Assignee" }, "fillOpacity": { "condition": { "param": "select", "value": 1 }, "value": 0.3 }, "strokeWidth": { "condition": [ { "param": "select", "empty": false, "value": 2 }, { "param": "highlight", "empty": false, "value": 1 } ], "value": 0 } } } ] }
示例图表

解决方案
一、Deneb甘特图是否支持分面?
Deneb基于Vega-Lite,本身支持分面(facet),但直接用facet是将图表拆分为独立子图,无法实现你需要的“同一轴上层级堆叠+展开/折叠”效果,因此不适用。
二、实现经办人+项目层级堆叠与展开/折叠的方法
通过调整Y轴层级编码、添加交互参数、优化任务条显示三个步骤实现需求:
1. 构建Y轴层级结构
将Assignee和Project组合为复合字段,让项目作为经办人的子项展示,避免同一经办人下的任务重叠:
"y": { "field": ["Assignee", "Project"], "type": "ordinal", "axis": { "title": null, "grid": true, "gridWidth": 2, "tickBand": "extent", "labelFontSize": 15, "labelColor": "#34282C", "zindex": 1 } }
2. 添加展开/折叠交互
通过定义参数控制项目的显示/隐藏,点击经办人即可切换其下项目的展开状态:
"params": [ { "name": "expandedAssignees", "value": [], "select": {"type": "point", "fields": ["Assignee"]}, "on": "click", "update": "datum.Assignee && indexof(expandedAssignees, datum.Assignee) > -1 ? remove(expandedAssignees, datum.Assignee) : concat(expandedAssignees, [datum.Assignee])" } ], "transform": [ { "filter": "indexof(expandedAssignees, datum.Assignee) > -1" } ]
3. 优化任务条显示
调整任务条高度、添加项目文本标签,让层级关系更清晰:
"layer": [ // 外层白色背景条 { "mark": { "type": "bar", "opacity": 1, "cornerRadius": 6, "height": 20, "tooltip": true, "color": "#FFFFFF" } }, // 内层任务条 { "mark": { "type": "bar", "opacity": 0.6, "cornerRadius": 6, "height": 15, "tooltip": true, "stroke": "black" }, "encoding": { "color": { "field": "Assignee", "legend": null } } }, // 任务文本 { "mark": { "type": "text", "align": "left", "dx": 5, "fontSize": 12 }, "encoding": { "text": {"field": "Task", "type": "nominal"} } }, // 项目文本(右侧标注) { "mark": { "type": "text", "align": "right", "dx": -5, "fontSize": 12, "fontWeight": "bold" }, "encoding": { "text": {"field": "Project", "type": "nominal"} } } ]
三、完整调整后的代码
{ "title": { "text": "按经办人-项目分组的甘特图", "fontSize": 16, "color": "#34282C" }, "data": {"name": "dataset"}, "params": [ { "name": "expandedAssignees", "value": [], "select": {"type": "point", "fields": ["Assignee"]}, "on": "click", "update": "datum.Assignee && indexof(expandedAssignees, datum.Assignee) > -1 ? remove(expandedAssignees, datum.Assignee) : concat(expandedAssignees, [datum.Assignee])" }, { "name": "grid", "select": "interval", "bind": "scales" }, { "name": "highlight", "select": {"type": "point", "on": "mouseover"} }, { "name": "select", "select": "point" } ], "transform": [ { "filter": "indexof(expandedAssignees, datum.Assignee) > -1" } ], "layer": [ { "transform": [ { "calculate": "now()", "as": "Today" } ], "mark": { "type": "rule", "color": "Sienna", "strokeDash": [0, 0], "size": 1, "tooltip": true }, "encoding": { "x": { "field": "Today", "type": "temporal" } } }, { "layer": [ { "mark": { "type": "bar", "opacity": 1, "cornerRadius": 6, "height": 20, "tooltip": true, "color": "#FFFFFF" } }, { "mark": { "type": "bar", "opacity": 0.6, "cornerRadius": 6, "height": 15, "tooltip": true, "stroke": "black" }, "encoding": { "color": { "field": "Assignee", "legend": null } } }, { "mark": { "type": "text", "align": "left", "dx": 5, "fontSize": 12 }, "encoding": { "text": { "field": "Task", "type": "nominal" } } }, { "mark": { "type": "text", "align": "right", "dx": -5, "fontSize": 12, "fontWeight": "bold" }, "encoding": { "text": { "field": "Project", "type": "nominal" } } } ], "encoding": { "y": { "field": ["Assignee", "Project"], "type": "ordinal", "axis": { "title": null, "grid": true, "gridWidth": 2, "tickBand": "extent", "labelFontSize": 15, "labelColor": "#34282C", "zindex": 1 } }, "x": { "field": "StartDate", "type": "temporal", "axis": { "title": null, "format": "%m/%y", "labelExpr": "[timeFormat(datum.value, '%m/%y'), timeFormat(datum.value, '%m') == '01' ? timeFormat(datum.value, '%Y') : '']", "orient": "top", "grid": true, "gridWidth": 2, "gridColor": "#ddd", "gridDash": { "condition": { "test": { "field": "value", "timeUnit": "month", "equal": 1 }, "value": [] }, "value": [2, 2] }, "labelOffset": 15, "labelAlign": "left", "labelPadding": 0 } }, "x2": {"field": "EndDate"}, "fillOpacity": { "condition": { "param": "select", "value": 1 }, "value": 0.3 }, "strokeWidth": { "condition": [ { "param": "select", "empty": false, "value": 2 }, { "param": "highlight", "empty": false, "value": 1 } ], "value": 0 } } } ] }
四、关键说明
- 复合Y轴字段
["Assignee", "Project"]自动构建层级结构,经办人作为父项、项目作为子项; expandedAssignees参数通过点击事件切换经办人下项目的显示状态,实现展开/折叠;- 双层任务条+左右文本标签,让任务归属的层级关系更直观。
内容的提问来源于stack exchange,提问作者nirnroot
相关产品推荐
相关产品推荐

