Available only in PRO Edition
此功能仅在 PRO 版本中可用
dhtmlxGantt 默认包含了内置功能,可以渲染诸如基线、截止日期和任务约束等额外元素。如果你希望扩展或调整这些功能,可以按照下述方法手动向时间线添加自定义元素。
添加额外元素通常涉及创建一个显示层,并使用绝对定位将自定义元素对齐到对应的任务上。
要为时间线区域添加额外层,请使用 addTaskLayer 方法。该方法接收一个函数作为参数,该函数:
gantt.addTaskLayer(function myNewElement(task) {
var el = document.createElement('div');
// your code
return el;
});
Related sample: Displaying deadlines
注意:
关于提升自定义元素渲染性能的建议,请参阅 addTaskLayer 文章。
如果你希望在每个时间线单元格中显示自定义内容,更简单高效的方式是直接使用 timeline_cell_content 模板将 HTML 插入到单元格中。
以下是一个使用该功能的示例:假设你希望同时展示任务的计划时间和实际时间。
最初,任务显示如下:
为了在任务条下方留出基线空间,将任务条高度减少到大约行高的一半:
gantt.config.bar_height = 16;
gantt.config.row_height = 40;
然后,使用如下 CSS 将任务条移动到行顶端:
.gantt_task_line, .gantt_line_wrapper {
margin-top: -9px;
}
.gantt_side_content {
margin-bottom: 7px;
}
.gantt_task_link .gantt_link_arrow {
margin-top: -12px
}
.gantt_side_content.gantt_right {
bottom: 0;
}
结果如下:
接下来,在任务对象中添加额外的数据属性,如 'planned_start' 和 'planned_end'。
dhtmlxGantt 会自动识别并解析 'start_date' 和 'end_date' 为 Date 对象。其他日期属性则需要手动解析。
要让 'planned_start' 和 'planned_end' 能被 dhtmlxGantt 使用,可以在 onTaskLoading 事件处理器中,使用 parseDate() 方法进行解析。
gantt.attachEvent("onTaskLoading", function(task){
task.planned_start = gantt.date.parseDate(task.planned_start, "xml_date");
task.planned_end = gantt.date.parseDate(task.planned_end, "xml_date");
return true;
});
然后,使用 addTaskLayer 方法,为每个任务(从 'planned_start' 到 'planned_end')显示计划时间。
gantt.addTaskLayer(function draw_planned(task) {
if (task.planned_start && task.planned_end) {
var sizes = gantt.getTaskPosition(task, task.planned_start, task.planned_end);
var el = document.createElement('div');
el.className = 'baseline';
el.style.left = sizes.left + 'px';
el.style.width = sizes.width + 'px';
el.style.top = sizes.top + gantt.config.task_height + 13 + 'px';
return el;
}
return false;
});
最后,为你的新元素添加 CSS 样式:
.baseline {
position: absolute;
border-radius: 2px;
opacity: 0.6;
margin-top: -7px;
height: 12px;
background: #ffd180;
border: 1px solid rgb(255,153,0);
}
为了让用户能够通过界面编辑新添加的属性,需要相应地重新定义 lightbox 的结构。
gantt.config.lightbox.sections = [
{name: "description", height: 70, map_to: "text", type: "textarea", focus: true},
{name: "time", height: 72, map_to: "auto", type: "duration"},
{name: "baseline", height: 72, map_to: {
start_date: "planned_start", end_date: "planned_end"}, type: "duration"}
];
gantt.locale.labels.section_baseline = "Planned";
你可以在相关示例中找到完整的代码示例。
Related sample: Display baselines
以下是一些示例,展示了如何使用 addTaskLayer() 方法,为 Gantt 图时间线增强多种自定义元素:
如果你希望为自定义元素启用拖放功能,需要了解 DHTMLX Gantt 并未内置该功能,但你可以通过一些简单的步骤手动实现。
基本思路是监听三个 DOM 事件(mousedown、mousemove、mouseup),并通过几个标志变量在这些事件之间跟踪拖放状态。
1. mousedown 事件表示可能开始拖放。但这也可能只是正常点击的开始,不应触发拖拽。此时设置标志表示请求拖拽,并保存初始鼠标位置及其他相关数据。
var dndRequested = false;
var dndActivated = false;
var startPosition = null;
var startTimestamp = null
var taskId = null;
var domUtils = gantt.utils.dom;
// 在本示例中,我们将在 `gantt.$task_data` 容器内拖动 `.baseline` 元素
gantt.event(gantt.$task_data, 'mousedown', function(e) {
// 使用 element.closest 或 gantt.utils.dom.closest 定位可拖拽元素
var draggableElement = domUtils.closest(e.target, '.baseline');
if (draggableElement) {
// 此时还无法确定用户是要拖拽还是仅点击
// 先存储事件信息,稍后在 'mousemove' 中判断
dndRequested = true;
startTimestamp = Date.now();
startPosition = domUtils.getRelativeEventPosition(e, gantt.$task_data);
taskId = draggableElement.getAttribute("data-task");
}
});
注意,事件处理器是通过 gantt.event 而不是原生 Element.addEventListener 附加的。这是因为当 Gantt 实例被 gantt.destructor 销毁时,所有通过 gantt.event 添加的处理器会自动清理。使用原生事件监听器则需要手动移除以避免内存泄漏。
2. 真正的拖放过程在 mousemove 事件中启动。不是在鼠标按下时立即开始拖动,而是将当前鼠标位置与之前保存的位置进行比较。只有当鼠标移动超过一定阈值时,才认为开始拖拽。你也可以判断鼠标按下的时间是否超过正常点击的时长。
拖动开始后,mousemove 处理器会更新被拖动元素的位置。对于通过 gantt.addTaskLayer
添加的元素,建议更新关联的任务数据,并通过 gantt.refreshTask 刷新任务,而不是直接操作 DOM。
gantt.event(window, 'mousemove', function(e) {
if (dndRequested && gantt.isTaskExists(taskId)) {
// 捕获到 'mousemove',且之前有 'mousedown' 事件
var currentPosition = domUtils.getRelativeEventPosition(e, gantt.$task_data);
if (!dndActivated) {
// 'mousemove' 可能只是普通点击过程的一部分,
// 不希望在普通点击时触发拖放
// 检查鼠标位置是否有显著变化,或按下时间是否超过正常点击
if(Math.abs(
currentPosition.x - startPosition.x) > 5 || (Date.now() - startTimestamp
) > 500) {
// 满足条件则认为开始拖拽
dndActivated = true;
}
}
if (dndActivated) {
// 这里可以更新被拖动元素的位置
// 对于通过 `gantt.addTaskLayer` 添加的元素,
// 建议更新任务对象并通过 `gantt.refreshTask` 重绘
// 也可以获取时间轴对应的日期:
var pointerDate = gantt.dateFromPos(currentPosition.x);
gantt.getTask(taskId).baseline_date = pointerDate;
gantt.refreshTask(taskId);
}
}
});
3. 最后监听 mouseup 事件。如果发生了拖拽,则通过日期取整、调用 gantt.updateTask 等操作完成变更,并重置所有临时标志变量。
gantt.event(window, 'mouseup', function(e) {
// 如果正在拖拽,则应用变更
if (dndActivated) {
// 校验并最终确定变更
var task = gantt.getTask(taskId);
task.baseline_date = gantt.roundDate({
date: task.baseline_date,
unit: "hour",
step: 1
});
// 调用 gantt.updateTask 以触发数据更新
gantt.updateTask(taskId);
}
// 清除之前设置的所有标志
dndRequested = false;
dndActivated = false;
startPosition = null;
startTimestamp = null;
taskId = null;
});
dhtmlxGantt 允许你在图表上添加额外图层,以放置自定义内容。该覆盖层可以是 div 容器、HTML canvas 或其他元素。你可以使用任何第三方库在其中渲染内容。
例如,你可以添加 S 曲线覆盖层,常用于可视化项目中的费用增长、材料消耗或整体进度。
要添加覆盖层,请按照以下两步操作:
gantt.plugins({
overlay: true
});
以下示例演示如何利用 ChartJS 库,在覆盖层中添加代表目标与实际项目进展的 S 曲线:
var overlay = gantt.ext.overlay.addOverlay(function(container){
var canvas = document.createElement("canvas");
container.appendChild(canvas);
canvas.style.height = container.offsetHeight + "px";
canvas.style.width = container.offsetWidth + "px";
var ctx = canvas.getContext("2d");
var myChart = new Chart(ctx, {
type: "line",
// 完整的图表配置
});
});
gantt.ext.overlay.addOverlay() 方法会返回新覆盖层的数字 id。
Related sample: Gantt chart with overlay and zoom (S-Curve)
dhtmlxgantt_overlay 扩展通过 gantt.ext.overlay 对象提供一组 API 方法,用于操作覆盖层。
向 Gantt 图添加一个新覆盖层并返回其 id。你需要提供一个接收自定义内容容器的函数。
var overlay = gantt.ext.overlay.addOverlay(function(container){});
根据 id 移除覆盖层。
gantt.ext.overlay.deleteOverlay(id);
返回图表中所有覆盖层的 id 数组。
var ids = gantt.ext.overlay.getOverlaysIds();
根据 id 重绘指定的覆盖层。
gantt.ext.overlay.refreshOverlay(id);
根据 id 显示覆盖层。
gantt.ext.overlay.showOverlay(id);
根据 id 隐藏覆盖层。
gantt.ext.overlay.hideOverlay(id);
检查指定覆盖层是否可见。如果可见,返回 true。
var isVisible = gantt.ext.overlay.isOverlayVisible(id);
Back to top