Popular features/Resource swimlanes
Loading live demo…from dhtmlxcode.com
Popular features
Resource swimlanes
Split the same task list into a lane per person, and drag a row into another lane to change its owner.
gantt.plugins({ grouping: true, tooltip: true });
gantt.config.date_format = "%Y-%m-%d";
gantt.config.date_grid = "%d %M";
gantt.config.open_tree_initially = true;
gantt.config.grid_resize = true;
gantt.config.order_branch = "marker";
gantt.config.row_height = 32;
gantt.config.bar_height = 20;
gantt.config.scale_height = 50;
gantt.config.min_column_width = 26;
gantt.config.initial_scroll = false;
gantt.config.scales = [
{ unit: "week", step: 1, format: "Week of %d %M" },
{ unit: "day", step: 1, format: "%d" }
];
/* ---------------------------------------------------------------------------
* TEAM
* ------------------------------------------------------------------------ */
let STAFF = [
{ key: 0, label: "Unassigned", color: "#94a3b8" },
{ key: 1, label: "Alena Ruiz", color: "#3b7be6" },
{ key: 2, label: "Boris Chen", color: "#7e57c2" },
{ key: 3, label: "Carmen Diaz", color: "#e8618c" },
{ key: 4, label: "Derek Cole", color: "#16a34a" },
{ key: 5, label: "Erin Wells", color: "#0ea5e9" }
];
function byId(list, id) {
for (let i = 0; i < list.length; i++) if (list[i].key == id) return list[i];
return null;
}
function avatar(person, size) {
return "<span class='av' style='background:" + person.color + (size ? ";width:" + size + "px;height:" + size + "px" : "") +
"' title='" + person.label + "'>" + DHX.ui.initials(person.label) + "</span>";
}
/* ---------------------------------------------------------------------------
* GRID
* ------------------------------------------------------------------------ */
gantt.config.columns = [
{ name: "text", label: "Task", tree: true, width: 250, resize: true, template: function (task) {
if (!task.$virtual) return task.text;
/* lane header: avatar + name + task count and total effort */
let person = byId(STAFF, task.id) || { label: task.text, color: "#94a3b8" };
let kids = [];
let days = 0;
gantt.eachTask(function (child) {
kids.push(child.id);
days += child.duration || 0;
}, task.id);
return "<span class='owner_chip'>" + avatar(person) + person.label +
"<span class='lane_meta'>" + kids.length + " tasks · " + days + "d</span></span>";
}},
{ name: "owner", label: "Owner", align: "center", width: 60, resize: true, template: function (task) {
if (task.$virtual || gantt.isSummaryTask(task)) return "";
let person = byId(STAFF, task.owner_id || 0);
return person ? "<span class='owner_chip'>" + avatar(person) + "</span>" : "";
}},
{ name: "start_date", label: "Start", align: "center", width: 100, resize: true, template: function (task) {
return task.$virtual ? "" : gantt.templates.date_grid(task.start_date, task);
}},
{ name: "duration", label: "Days", align: "center", width: 54, resize: true, template: function (task) {
return (task.$virtual || gantt.isSummaryTask(task)) ? "" : task.duration;
}},
{ name: "add", width: 40, resize: true }
];
gantt.config.grid_width = 500;
gantt.config.lightbox.sections = [
{ name: "description", height: 38, map_to: "text", type: "textarea", focus: true },
{ name: "owner", height: 22, map_to: "owner_id", type: "select", options: STAFF },
{ name: "time", type: "duration", map_to: "auto" }
];
gantt.locale.labels.section_owner = "Owner";
/* ---------------------------------------------------------------------------
* TEMPLATES
* ------------------------------------------------------------------------ */
gantt.templates.timeline_cell_class = function (task, date) {
return gantt.isWorkTime({ date, task }) ? "" : "week_end";
};
gantt.templates.task_class = function (start, end, task) {
if (task.$virtual) return "lane_bar";
if (gantt.isSummaryTask(task)) return "phase_bar";
return "owner_" + (task.owner_id || 0);
};
gantt.templates.grid_row_class = gantt.templates.task_row_class = function (start, end, task) {
return task.$virtual ? "lane_row" : "";
};
gantt.templates.rightside_text = function (start, end, task) {
if (task.$virtual || gantt.isSummaryTask(task)) return "";
let person = byId(STAFF, task.owner_id || 0);
return person && person.key ? person.label.split(" ")[0] : "";
};
gantt.templates.tooltip_text = function (start, end, task) {
let person = byId(STAFF, task.owner_id || 0);
return "<b>" + task.text + "</b><br>" + (person ? person.label + "<br>" : "") +
gantt.templates.tooltip_date_format(start) + " – " + gantt.templates.tooltip_date_format(end);
};
/* ---------------------------------------------------------------------------
* VIEW SWITCH: lanes (groupBy owner) vs plain project tree
* ------------------------------------------------------------------------ */
let laneMode = true;
/* in lane mode the sprint summary rows are redundant, so hide them */
gantt.attachEvent("onBeforeTaskDisplay", function (id, task) {
if (laneMode && gantt.isSummaryTask(task) && !task.$virtual) return false;
return true;
});
function setView(mode) {
laneMode = (mode === "lanes");
DHX.ui.setActiveByAttribute("#view_seg .dhx_btn", "data-view", mode, "active");
gantt.config.show_links = !laneMode; // cross-lane link lines only clutter the lanes
if (laneMode) {
gantt.groupBy({
groups: STAFF,
relation_property: "owner_id",
group_id: "key",
group_text: "label"
});
} else {
gantt.groupBy(false);
}
}
/* ---------------------------------------------------------------------------
* DATA: three two-week sprints plus an unassigned backlog
* ------------------------------------------------------------------------ */
let demoData = {
tasks: [
{ id: 100, text: "Sprint 61 (Jul 6 - 17)", type: "project", open: true },
{ id: 101, text: "Payment retries", parent: 100, start_date: "2026-07-06", duration: 4, progress: 1, owner_id: 1 },
{ id: 102, text: "Rate limiting", parent: 100, start_date: "2026-07-06", duration: 5, progress: 0.9, owner_id: 2 },
{ id: 103, text: "Onboarding emails", parent: 100, start_date: "2026-07-07", duration: 3, progress: 1, owner_id: 3 },
{ id: 104, text: "Audit log UI", parent: 100, start_date: "2026-07-06", duration: 6, progress: 0.8, owner_id: 4 },
{ id: 105, text: "Search facets", parent: 100, start_date: "2026-07-08", duration: 5, progress: 0.7, owner_id: 5 },
{ id: 106, text: "Bugfix rotation", parent: 100, start_date: "2026-07-10", duration: 5, progress: 0.6, owner_id: 3 },
{ id: 200, text: "Sprint 62 (Jul 20 - 31)", type: "project", open: true },
{ id: 201, text: "Payment webhooks", parent: 200, start_date: "2026-07-20", duration: 5, progress: 0.4, owner_id: 1 },
{ id: 202, text: "API pagination", parent: 200, start_date: "2026-07-20", duration: 4, progress: 0.3, owner_id: 2 },
{ id: 203, text: "Team invites", parent: 200, start_date: "2026-07-21", duration: 5, progress: 0.25, owner_id: 3 },
{ id: 204, text: "Audit log export", parent: 200, start_date: "2026-07-20", duration: 3, progress: 0.5, owner_id: 4 },
{ id: 205, text: "Search synonyms", parent: 200, start_date: "2026-07-22", duration: 5, progress: 0.2, owner_id: 5 },
{ id: 206, text: "Perf profiling", parent: 200, start_date: "2026-07-27", duration: 4, progress: 0, owner_id: 4 },
{ id: 300, text: "Sprint 63 (Aug 3 - 14)", type: "project", open: true },
{ id: 301, text: "Billing portal", parent: 300, start_date: "2026-08-03", duration: 6, progress: 0, owner_id: 1 },
{ id: 302, text: "Rate plan editor", parent: 300, start_date: "2026-08-04", duration: 5, progress: 0, owner_id: 2 },
{ id: 303, text: "Email digests", parent: 300, start_date: "2026-08-03", duration: 4, progress: 0, owner_id: 3 },
{ id: 304, text: "SSO (SAML)", parent: 300, start_date: "2026-08-05", duration: 6, progress: 0, owner_id: 5 },
{ id: 305, text: "Load testing", parent: 300, start_date: "2026-08-10", duration: 4, progress: 0, owner_id: 4 },
{ id: 306, text: "Release hardening", parent: 300, start_date: "2026-08-11", duration: 4, progress: 0, owner_id: 2 },
{ id: 307, text: "Invoice PDF export", parent: 300, start_date: "2026-08-06", duration: 4, progress: 0, owner_id: 1 },
{ id: 308, text: "Search reindexing", parent: 300, start_date: "2026-08-12", duration: 3, progress: 0, owner_id: 5 },
{ id: 400, text: "Backlog", type: "project", open: true },
{ id: 401, text: "Dark mode", parent: 400, start_date: "2026-08-17", duration: 5, progress: 0, owner_id: 0 },
{ id: 402, text: "CSV importer", parent: 400, start_date: "2026-08-17", duration: 4, progress: 0, owner_id: 0 },
{ id: 403, text: "Mobile nav revamp", parent: 400, start_date: "2026-08-18", duration: 3, progress: 0, owner_id: 0 },
{ id: 404, text: "Accessibility audit", parent: 400, start_date: "2026-08-19", duration: 3, progress: 0, owner_id: 0 }
],
links: [
{ id: 1, source: 101, target: 201, type: "0" },
{ id: 2, source: 201, target: 301, type: "0" },
{ id: 3, source: 102, target: 202, type: "0" },
{ id: 4, source: 104, target: 204, type: "0" },
{ id: 5, source: 105, target: 205, type: "0" },
{ id: 6, source: 205, target: 304, type: "0" },
{ id: 7, source: 302, target: 306, type: "0" },
{ id: 8, source: 305, target: 306, type: "0" }
]
};
/* ---------------------------------------------------------------------------
* INIT
* ------------------------------------------------------------------------ */
gantt.init("gantt_here");
gantt.parse(demoData);
setView("lanes");
/* footer: the team roster */
document.getElementById("footer_team").innerHTML = STAFF.filter(function (p) { return p.key; })
.map(function (p) {
return "<span class='lg'><i class='sw' style='background:" + p.color + "'></i>" + p.label + "</span>";
}).join("");<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Team lanes: project broken down by assignee</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: 4px; }
.gantt_grid_scale .gantt_grid_head_cell { font-weight: 600; }
.gantt_task_cell.week_end { background: rgba(15,23,42,.045); }
:root[data-gantt-theme="dark"] .gantt_task_cell.week_end { background: rgba(255,255,255,.04); }
.gantt_task_line.owner_1 { background: #3b7be6; }
.gantt_task_line.owner_2 { background: #7e57c2; }
.gantt_task_line.owner_3 { background: #e8618c; }
.gantt_task_line.owner_4 { background: #16a34a; }
.gantt_task_line.owner_5 { background: #0ea5e9; }
.gantt_task_line.owner_0 { background: #94a3b8; }
.lane_row, .lane_row.odd { background: rgba(226,232,240,.45); font-weight: 600; }
:root[data-gantt-theme="dark"] .lane_row, :root[data-gantt-theme="dark"] .lane_row.odd { background: rgba(255,255,255,.06); }
.gantt_task_line.lane_bar { background: #94a3b8; opacity: .3; border-radius: 3px; }
:root[data-gantt-theme="dark"] .gantt_task_line.lane_bar { background: #5a6475; opacity: 1; }
.gantt_task_line.lane_bar .gantt_task_progress { display: none; }
.gantt_task_line.phase_bar { background: #334155; opacity: .85; }
:root[data-gantt-theme="dark"] .gantt_task_line.phase_bar { background: #8fa3bd; }
.owner_chip { display: inline-flex; align-items: center; gap: 7px; }
.owner_chip .av {
width: 22px; height: 22px; border-radius: 50%; flex: none;
display: inline-flex; align-items: center; justify-content: center;
font: 600 10px var(--dhx-font); color: #fff;
}
.lane_meta { font-weight: 400; color: var(--dhx-muted); margin-left: 6px; }
.footer_legend { display: inline-flex; align-items: center; gap: 12px; }
.footer_legend .lg { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; }
.footer_legend .sw { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
</style>
</head>
<body>
<!-- Header -->
<div class="dhx_header" style="position:relative; justify-content:center; gap:14px">
<span class="dhx_demo_hint" style="position:absolute; left:14px; top:50%; transform:translateY(-50%)">Every person is a lane: drag a row into another lane to reassign the task.</span>
<div class="dhx_seg" id="view_seg">
<button class="dhx_btn active" data-view="lanes" onclick="setView('lanes')">Team lanes</button>
<button class="dhx_btn" data-view="tree" onclick="setView('tree')">Project tree</button>
</div>
</div>
<div id="gantt_here"></div>
<!-- Footer -->
<div class="dhx_footer">
<div class="footer_legend" id="footer_team"></div>
<div class="dhx_footer__right"><span>Grouping is native: one gantt.groupBy() call, no data reshaping</span></div>
</div>
</body>
</html>