Skip to main content
Back to examples
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 &middot; " + 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) + " &ndash; " + 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>