Popular features/Baselines and recovery
Loading live demo…from dhtmlxcode.com
Popular features
Baselines and recovery
Keep one approved baseline and switch the live schedule between the contract plan, a delayed forecast, and a recovery plan.
gantt.plugins({ tooltip: true });
gantt.message({
text: "Blue tasks are still on baseline. Red tasks are late. Green tasks show the recovery forecast.",
expire: -1
});
gantt.config.date_format = "%Y-%m-%d";
gantt.config.open_tree_initially = true;
gantt.config.row_height = 40;
gantt.config.bar_height = 24;
gantt.config.scale_height = 50;
gantt.config.grid_width = 500;
gantt.config.baselines.render_mode = "separateRow";
gantt.config.baselines.row_height = 16;
gantt.config.baselines.bar_height = 9;
gantt.config.scales = [
{ unit: "month", step: 1, format: "%F %Y" },
{ unit: "week", step: 1, format: "%d %M" }
];
let currentScenario = "current";
let scenarios = {
contract: { label: "Contract plan", shifts: { 11: 0, 12: 0, 13: 0, 14: 0, 21: 0, 23: 0, 22: 0, 24: 0, 31: 0, 33: 0, 32: 0, 34: 0, 41: 0, 43: 0, 42: 0, 44: 0, 50: 0 } },
current: { label: "Current delay", shifts: { 11: 0, 12: 2, 13: 3, 14: 4, 21: 4, 23: 6, 22: 7, 24: 7, 31: 8, 33: 9, 32: 9, 34: 10, 41: 11, 43: 11, 42: 11, 44: 12, 50: 12 } },
recovery: { label: "Recovery plan", shifts: { 11: 0, 12: 2, 13: 3, 14: 3, 21: 4, 23: 5, 22: 5, 24: 4, 31: 5, 33: 5, 32: 4, 34: 4, 41: 4, 43: 3, 42: 3, 44: 3, 50: 3 } }
};
let contractTasks = [
{ id: 1, text: "Hotel opening program", type: "project", open: true },
{ id: 10, text: "Guest rooms", type: "project", parent: 1, open: true },
{ id: 11, text: "Model room approval", owner: "Client", parent: 10, start: "2026-04-06", duration: 5, progress: 1 },
{ id: 12, text: "Room furniture install", owner: "Vendor", parent: 10, start: "2026-04-13", duration: 10, progress: .75 },
{ id: 13, text: "Bathroom fixtures install", owner: "Vendor", parent: 10, start: "2026-04-16", duration: 8, progress: .6 },
{ id: 14, text: "Corridor carpet and paint", owner: "Field", parent: 10, start: "2026-04-24", duration: 6, progress: .3 },
{ id: 20, text: "Public areas", type: "project", parent: 1, open: true },
{ id: 21, text: "Lobby stone delivery", owner: "Supply", parent: 20, start: "2026-04-27", duration: 7, progress: .55 },
{ id: 23, text: "Lobby lighting install", owner: "Field", parent: 20, start: "2026-05-04", duration: 6, progress: .4 },
{ id: 22, text: "Restaurant fit-out", owner: "Field", parent: 20, start: "2026-05-06", duration: 12, progress: .25 },
{ id: 24, text: "Signage and wayfinding", owner: "Vendor", parent: 20, start: "2026-05-13", duration: 5, progress: .15 },
{ id: 30, text: "Systems", type: "project", parent: 1, open: true },
{ id: 31, text: "Fire alarm commissioning", owner: "MEP", parent: 30, start: "2026-05-18", duration: 6, progress: .2 },
{ id: 33, text: "HVAC balancing", owner: "MEP", parent: 30, start: "2026-05-20", duration: 8, progress: .1 },
{ id: 32, text: "Elevator inspection", owner: "Authority", parent: 30, start: "2026-05-27", duration: 4, progress: 0 },
{ id: 34, text: "Life-safety systems test", owner: "Authority", parent: 30, start: "2026-05-29", duration: 5, progress: 0 },
{ id: 40, text: "Opening readiness", type: "project", parent: 1, open: true },
{ id: 41, text: "Staff training", owner: "Ops", parent: 40, start: "2026-06-02", duration: 5, progress: 0 },
{ id: 43, text: "Punch list walkthrough", owner: "Client", parent: 40, start: "2026-06-05", duration: 4, progress: 0 },
{ id: 42, text: "Soft opening", owner: "Ops", parent: 40, start: "2026-06-10", duration: 3, progress: 0 },
{ id: 44, text: "Marketing photography", owner: "Ops", parent: 40, start: "2026-06-08", duration: 3, progress: 0 },
{ id: 50, text: "Hotel opens", owner: "GM", parent: 1, start: "2026-06-15", type: "milestone", progress: 0 }
];
let links = [
{ id: 1, source: 11, target: 12, type: "0" },
{ id: 2, source: 12, target: 21, type: "0" },
{ id: 3, source: 21, target: 22, type: "0" },
{ id: 4, source: 22, target: 31, type: "0" },
{ id: 5, source: 31, target: 32, type: "0" },
{ id: 6, source: 32, target: 41, type: "0" },
{ id: 7, source: 41, target: 42, type: "0" },
{ id: 8, source: 42, target: 50, type: "0" },
{ id: 9, source: 12, target: 13, type: "0" },
{ id: 10, source: 13, target: 14, type: "0" },
{ id: 11, source: 21, target: 23, type: "0" },
{ id: 12, source: 22, target: 24, type: "0" },
{ id: 13, source: 31, target: 33, type: "0" },
{ id: 14, source: 32, target: 34, type: "0" },
{ id: 15, source: 41, target: 43, type: "0" },
{ id: 16, source: 42, target: 44, type: "0" }
];
const monthDayToStr = gantt.date.date_to_str("%d %M");
const dateToStr = gantt.date.date_to_str("%Y-%m-%d");
const strToDate = gantt.date.str_to_date("%Y-%m-%d");
function addDays(date, days) {
let next = strToDate(date);
return dateToStr(gantt.date.add(next, days, "day"));
}
function makeData(mode) {
let shifts = scenarios[mode].shifts;
let data = contractTasks.map(function (task) {
let copy = {};
for (let key in task) copy[key] = task[key];
if (task.start) {
copy.start_date = addDays(task.start, shifts[task.id] || 0);
copy.baseline_start = task.start;
}
delete copy.start;
return copy;
});
let baselines = contractTasks
.filter(function (task) { return task.start; })
.map(function (task, index) {
let start = gantt.date.parseDate(task.start, "%Y-%m-%d");
let end = task.type === "milestone" ? new Date(start) : gantt.calculateEndDate({ start_date: start, duration: task.duration });
return { id: index + 1, task_id: task.id, start_date: task.start, end_date: dateToStr(end) };
});
return { tasks: data, baselines, links };
}
function baselineFor(task) {
let list = task.baselines || [];
return list.length ? list[0] : null;
}
function slipDays(task) {
let bl = baselineFor(task);
if (!bl) return null;
const minDate = new Date(Math.min(task.end_date.valueOf(), bl.end_date.valueOf()));
const maxDate = new Date(Math.max(task.end_date.valueOf(), bl.end_date.valueOf()));
const sign = task.end_date > bl.end_date ? 1 : -1;
return sign * gantt.calculateDuration({ start_date: minDate, end_date: maxDate, task });
}
gantt.templates.task_class = function (start, end, task) {
if (gantt.isSummaryTask(task)) return "";
let slip = slipDays(task);
if (slip <= 0) return "on_baseline";
return currentScenario === "recovery" ? "recovery_forecast" : "late_forecast";
};
gantt.templates.tooltip_text = function (start, end, task) {
let html = `<b>${task.text}</b><br>${scenarios[currentScenario].label}: ${monthDayToStr(start)} – ${monthDayToStr(end)}`;
let bl = baselineFor(task);
if (bl) {
let slip = slipDays(task);
html += `<br>Baseline: ${monthDayToStr(bl.start_date)} – ${monthDayToStr(bl.end_date)}`;
html += slip > 0 ? `<br>Forecast slip +${slip}d` : "<br>On baseline";
}
return html;
};
gantt.config.columns = [
{ name: "text", label: "Work package", tree: true, width: 220, resize: true },
{ name: "owner", label: "Owner", width: 86, align: "center", resize: true },
{ name: "end_date", label: "Forecast finish", width: 110, align: "center", resize: true,
template: function (task) { return gantt.isSummaryTask(task) ? "" : monthDayToStr(task.end_date); } },
{ name: "delta", label: "vs baseline", width: 92, align: "center", resize: true, template: function (task) {
if (gantt.isSummaryTask(task)) return "";
let slip = slipDays(task);
let label = slip <= 0 ? "on plan" : `+${slip}d`;
return `<span class='delta_chip ${slip > 0 ? "bad" : "good"}'>${label}</span>`;
}}
];
function setScenario(mode) {
currentScenario = mode;
DHX.ui.setActiveByAttribute("#scenario_seg .dhx_btn", "data-mode", mode, "active");
gantt.clearAll();
gantt.parse(makeData(mode));
gantt.batchUpdate(function () {
gantt.eachTask(function (task) { gantt.adjustTaskHeightForBaselines(task); });
});
updateSummary();
gantt.showDate(new Date(2026, 3, 27));
}
function updateSummary() {
let finish = gantt.getTask(50);
let finishSlip = slipDays(finish);
let recovered = scenarios.current.shifts[50] - scenarios.recovery.shifts[50];
let text = `${scenarios[currentScenario].label} \u00b7 opening ${monthDayToStr(finish.start_date)}`;
text += finishSlip > 0 ? ` (+${finishSlip}d vs baseline)` : " (on baseline)";
if (currentScenario === "recovery") text += ` \u00b7 ${recovered} days recovered`;
DHX.ui.setText("footer_var", text);
}
gantt.init("gantt_here");
setScenario("current");<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Baseline recovery plan: dhtmlxGantt</title>
<script src="../../codebase/dhtmlxgantt.js?v=10.0.0"></script>
<link rel="stylesheet" href="../../codebase/dhtmlxgantt.css?v=10.0.0">
<link rel="stylesheet" href="../common/demo-controls/dhx_controls.css">
<script src="../common/demo-controls/dhx_controls.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<style>
body { display: flex; flex-direction: column; }
#gantt_here { width: 100%; flex: 1 1 auto; min-height: 0; }
.gantt_task_line { border: none; border-radius: 6px; }
.gantt_task_baseline {
background: rgba(100,116,139,.22) !important;
border: 1.5px dashed rgba(71,85,105,.75) !important;
border-radius: 4px !important;
opacity: 1 !important;
}
:root[data-gantt-theme="dark"] .gantt_task_baseline {
background: #5a6475 !important;
border: none !important;
}
.gantt_task_line.on_baseline { background: #2563eb; }
.gantt_task_line.late_forecast { background: #d94b4b; }
.gantt_task_line.recovery_forecast { background: #14a076; }
.delta_chip { display: inline-block; padding: 1px 9px; border-radius: 10px; font: 600 11px var(--dhx-font); font-variant-numeric: tabular-nums; }
.delta_chip.good { background: var(--dhx-ok-bg); color: var(--dhx-ok-ink); }
.delta_chip.bad { background: var(--dhx-danger-bg); color: var(--dhx-danger-ink); }
.footer_legend { display: inline-flex; gap: 16px; flex-wrap: wrap; }
.footer_legend .lg { display: inline-flex; align-items: center; gap: 6px; }
.footer_legend .sw { width: 16px; height: 10px; border-radius: 2px; display: inline-block; }
.footer_legend .baseline_task { background: #2563eb; }
.footer_legend .current { background: #d94b4b; }
.footer_legend .recovery { background: #14a076; }
.footer_legend .baseline { background: rgba(100,116,139,.22); border: 1.5px dashed rgba(71,85,105,.75); }
:root[data-gantt-theme="dark"] .footer_legend .baseline { background: #5a6475; border: none; }
.gantt_task_cell.week_end {
background-color: var(--dhx-gantt-base-colors-background-alt);
}
.gantt_task_row.gantt_selected .gantt_task_cell.week_end {
background-color: var(--dhx-gantt-base-colors-select);
}
</style>
</head>
<body>
<div class="dhx_header dhx_header--centered">
<div class="dhx_seg" id="scenario_seg">
<button class="dhx_btn" data-mode="contract" onclick="setScenario('contract')">Contract</button>
<button class="dhx_btn active" data-mode="current" onclick="setScenario('current')">Current delay</button>
<button class="dhx_btn" data-mode="recovery" onclick="setScenario('recovery')">Recovery plan</button>
</div>
</div>
<div id="gantt_here"></div>
<div class="dhx_footer">
<div class="footer_legend">
<span class="lg"><i class="sw baseline_task"></i> On baseline</span>
<span class="lg"><i class="sw current"></i> Late forecast</span>
<span class="lg"><i class="sw recovery"></i> Recovery forecast</span>
<span class="lg"><i class="sw baseline"></i> Saved baseline</span>
</div>
<div class="dhx_footer__right"><span id="footer_var"></span></div>
</div>
</body>
</html>