Skip to main content
Back to examples
Popular features/Schedule direction
Loading live demo…from dhtmlxcode.com
Popular features

Schedule direction

Run auto scheduling from the start date or back from the deadline, and compare dependency only scheduling with task constraints.

gantt.plugins({
	auto_scheduling: true,
	marker: true,
	tooltip: true
});

gantt.message({
	text: "Switch the direction to move spare time between the project edges.<br><br>Disable constraints to compare dependency-only scheduling with constrained scheduling.<br><br>Double-click a task to edit its constraint.",
	expire: -1
});

gantt.config.date_format = "%Y-%m-%d";
gantt.config.work_time = true;
gantt.config.fit_tasks = false;
gantt.config.row_height = 36;
gantt.config.bar_height = 24;
gantt.config.scale_height = 52;
gantt.config.min_column_width = 34;
gantt.config.grid_width = 650;
gantt.config.grid_resize = true;

function mondayOnOrAfter(date) {
	date = gantt.date.day_start(new Date(date));
	while (date.getDay() !== 1) date = gantt.date.add(date, 1, "day");
	return date;
}

const PROJECT_START = mondayOnOrAfter(new Date());
const PROJECT_END = gantt.date.add(PROJECT_START, 46, "day");
const dateToData = gantt.date.date_to_str("%Y-%m-%d");
const dateToGrid = gantt.date.date_to_str("%d %M");
const SEED = dateToData(PROJECT_START);

function offsetFromStart(days) {
	return gantt.date.add(PROJECT_START, days, "day");
}

function offsetFromEnd(days) {
	return gantt.date.add(PROJECT_END, days, "day");
}

gantt.config.project_start = PROJECT_START;
gantt.config.project_end = PROJECT_END;
gantt.config.start_date = gantt.date.add(PROJECT_START, -2, "day");
gantt.config.end_date = gantt.date.add(PROJECT_END, 4, "day");

gantt.config.scales = [
	{ unit: "month", step: 1, format: "%F %Y" },
	{ unit: "day", step: 1, format: "%j %D" }
];

gantt.templates.scale_cell_class = function (date) {
	return gantt.isWorkTime(date) ? "" : "weekend";
};
gantt.templates.timeline_cell_class = function (task, date) {
	return gantt.isWorkTime({ date: date, task: task }) ? "" : "weekend";
};

const constraintLabels = {
	asap: "ASAP",
	alap: "ALAP",
	snet: "SNET",
	snlt: "SNLT",
	fnet: "FNET",
	fnlt: "FNLT",
	mso: "MSO",
	mfo: "MFO"
};

function constraintTemplate(task) {
	if (task.type === gantt.config.types.project || !task.constraint_type) return "";
	const type = constraintLabels[task.constraint_type] || String(task.constraint_type).toUpperCase();
	const date = task.constraint_date ? ` · ${dateToGrid(task.constraint_date)}` : "";
	const exact = task.constraint_type === "mso" || task.constraint_type === "mfo";
	return `<div class="constraint_cell"><span class="constraint_badge${exact ? " constraint_badge--exact" : ""}">${type}${date}</span></div>`;
}

gantt.config.columns = [
	{ name: "text", label: "Task", tree: true, width: 220, resize: true },
	{ name: "start_date", label: "Start", align: "center", width: 90, resize: true,
		template: function (task) { return task.type === gantt.config.types.project ? "" : gantt.templates.date_grid(task.start_date, task); } },
	{ name: "end_date", label: "End", align: "center", width: 90, resize: true,
		template: function (task) { return task.type === gantt.config.types.project ? "" : gantt.templates.date_grid(task.end_date, task); } },
	{ name: "duration", label: "Days", align: "center", width: 55, resize: true,
		editor: { type: "number", map_to: "duration", min: 1, max: 60 } },
	{ name: "constraint", label: "Constraint", align: "center", width: 155, resize: true, template: constraintTemplate },
	{ name: "add", width: 40, resize: true }
];

gantt.locale.labels.section_constraint = "Constraint";
gantt.config.lightbox.sections = [
	{ name: "description", height: 44, map_to: "text", type: "textarea", focus: true },
	{ name: "constraint", type: "constraint" },
	{ name: "time", map_to: "auto", type: "duration" }
];
gantt.config.lightbox.project_sections = [
	{ name: "description", height: 44, map_to: "text", type: "textarea", focus: true },
	{ name: "time", map_to: "auto", type: "duration" }
];

const data = {
	tasks: [
		{ id: 1, text: "Website Relaunch", type: "project", open: true, progress: 0.3 },
		{ id: 11, text: "Kickoff & scope", parent: 1, start_date: SEED, duration: 3, progress: 0.6 },
		{ id: 12, text: "UX & visual design", parent: 1, start_date: SEED, duration: 6, progress: 0.3 },
		{ id: 13, text: "Front-end build", parent: 1, start_date: SEED, duration: 8, progress: 0 },
		{ id: 14, text: "Content & SEO", parent: 1, start_date: SEED, duration: 5, progress: 0,
			constraint_type: "snet", constraint_date: offsetFromStart(14) },
		{ id: 15, text: "QA & fixes", parent: 1, start_date: SEED, duration: 4, progress: 0 },
		{ id: 16, text: "Launch prep", parent: 1, start_date: SEED, duration: 2, progress: 0,
			constraint_type: "fnet", constraint_date: offsetFromStart(30) },
		{ id: 17, text: "Go live", parent: 1, start_date: SEED, type: "milestone",
			constraint_type: "mso", constraint_date: offsetFromStart(40) },

		{ id: 2, text: "Marketing & Launch Campaign", type: "project", open: true, progress: 0.2 },
		{ id: 21, text: "Campaign kickoff & brief", parent: 2, start_date: SEED, duration: 2, progress: 0.5 },
		{ id: 22, text: "Brand messaging & assets", parent: 2, start_date: SEED, duration: 5, progress: 0.2 },
		{ id: 23, text: "Landing page copy", parent: 2, start_date: SEED, duration: 4, progress: 0 },
		{ id: 24, text: "Paid media plan", parent: 2, start_date: SEED, duration: 3, progress: 0 },
		{ id: 25, text: "Social content calendar", parent: 2, start_date: SEED, duration: 4, progress: 0 },
		{ id: 26, text: "Email sequence draft", parent: 2, start_date: SEED, duration: 3, progress: 0 },
		{ id: 27, text: "Influencer outreach", parent: 2, start_date: SEED, duration: 4, progress: 0 },
		{ id: 28, text: "Ad creative production", parent: 2, start_date: SEED, duration: 5, progress: 0 },
		{ id: 29, text: "Landing page build", parent: 2, start_date: SEED, duration: 6, progress: 0 },
		{ id: 30, text: "Analytics & tracking setup", parent: 2, start_date: SEED, duration: 2, progress: 0 },
		{ id: 31, text: "Internal review & sign-off", parent: 2, start_date: SEED, duration: 2, progress: 0 },
		{ id: 32, text: "Press release draft", parent: 2, start_date: SEED, duration: 3, progress: 0 },
		{ id: 33, text: "Legal & compliance check", parent: 2, start_date: SEED, duration: 2, progress: 0,
			constraint_type: "snlt", constraint_date: offsetFromEnd(-16) },
		{ id: 34, text: "Channel scheduling", parent: 2, start_date: SEED, duration: 2, progress: 0 },
		{ id: 35, text: "Final QA pass", parent: 2, start_date: SEED, duration: 3, progress: 0,
			constraint_type: "fnlt", constraint_date: offsetFromEnd(-7) },
		{ id: 36, text: "Launch day coordination", parent: 2, start_date: SEED, duration: 1, progress: 0 },
		{ id: 37, text: "Campaign live", parent: 2, start_date: SEED, type: "milestone",
			constraint_type: "mfo", constraint_date: PROJECT_END }
	],
	links: [
		{ id: 1, source: 11, target: 12, type: "0" },
		{ id: 2, source: 12, target: 13, type: "0" },
		{ id: 3, source: 11, target: 14, type: "0" },
		{ id: 4, source: 13, target: 15, type: "0" },
		{ id: 5, source: 14, target: 15, type: "0" },
		{ id: 6, source: 15, target: 16, type: "0" },
		{ id: 7, source: 16, target: 17, type: "0" },

		{ id: 8, source: 21, target: 22, type: "0" },
		{ id: 9, source: 21, target: 23, type: "0" },
		{ id: 10, source: 21, target: 24, type: "0" },
		{ id: 11, source: 21, target: 30, type: "0" },
		{ id: 12, source: 22, target: 28, type: "0" },
		{ id: 13, source: 23, target: 29, type: "0" },
		{ id: 14, source: 24, target: 25, type: "0" },
		{ id: 15, source: 25, target: 26, type: "0" },
		{ id: 16, source: 26, target: 27, type: "0" },
		{ id: 17, source: 27, target: 34, type: "0" },
		{ id: 18, source: 30, target: 31, type: "0" },
		{ id: 19, source: 31, target: 32, type: "0" },
		{ id: 20, source: 32, target: 33, type: "0" },
		{ id: 21, source: 28, target: 35, type: "0" },
		{ id: 22, source: 29, target: 35, type: "0" },
		{ id: 23, source: 34, target: 35, type: "0" },
		{ id: 24, source: 33, target: 35, type: "0" },
		{ id: 25, source: 35, target: 36, type: "0" },
		{ id: 26, source: 36, target: 37, type: "0" }
	]
};

const startMarkerId = gantt.addMarker({ start_date: PROJECT_START, text: "Project start", css: "anchor_marker" });
const endMarkerId = gantt.addMarker({ start_date: PROJECT_END, text: "Deadline", css: "anchor_marker" });

let currentDirection = "forward";
let constraintsEnabled = true;

function updateAnchorUI(fromEnd) {
	const startMarker = gantt.getMarker(startMarkerId);
	const endMarker = gantt.getMarker(endMarkerId);

	if (startMarker) {
		startMarker.css = "anchor_marker" + (fromEnd ? "" : " active_anchor");
		gantt.updateMarker(startMarkerId);
	}
	if (endMarker) {
		endMarker.css = "anchor_marker" + (fromEnd ? " active_anchor" : "");
		gantt.updateMarker(endMarkerId);
	}

	DHX.ui.setActiveByAttribute("#dir_seg .dhx_btn", "data-dir", fromEnd ? "backward" : "forward", "active");
}

function configureAutoScheduling() {
	const fromEnd = currentDirection === "backward";
	gantt.config.auto_scheduling = {
		enabled: true,
		show_constraints: true,
		gap_behavior: "compress",
		apply_constraints: constraintsEnabled,
		schedule_from_end: fromEnd
	};

	gantt.autoSchedule();
	updateAnchorUI(fromEnd);
	DHX.ui.setActiveByAttribute("#constraint_seg .dhx_btn", "data-constraints", constraintsEnabled ? "on" : "off", "active");
	document.getElementById("direction_status").textContent =
		`${fromEnd ? "Backward" : "Forward"} planning · constraints ${constraintsEnabled ? "applied" : "ignored"}`;
}

function applyDirection(direction) {
	currentDirection = direction === "backward" ? "backward" : "forward";
	configureAutoScheduling();
}

function setConstraints(enabled) {
	constraintsEnabled = !!enabled;
	configureAutoScheduling();
}

gantt.config.auto_scheduling = {
	enabled: true,
	show_constraints: true,
	gap_behavior: "compress",
	apply_constraints: true,
	schedule_from_end: false
};

gantt.init("gantt_here");
gantt.parse(data);
configureAutoScheduling();
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Schedule direction &amp; task constraints | 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 { flex: 1 1 auto; min-height: 0; width: 100%; }

		.gantt_task_line { border: none; border-radius: 7px; box-shadow: 0 1px 2px rgba(16,24,40,.18); }
		.gantt_task_line .gantt_task_progress { border-radius: 7px 0 0 7px; }
		.gantt_task_line.gantt_project { border-radius: 5px; }

		.gantt_task_cell.weekend,
		.gantt_scale_cell.weekend { background-color: rgba(15,23,42,.04); }

		.gantt_marker.anchor_marker { background: #b6bdc7; opacity: 1; }
		.gantt_marker.anchor_marker .gantt_marker_content { color: #fff; font-weight: 600; }
		.gantt_marker.anchor_marker.active_anchor { background: #e5484d; }

		.schedule_header {
			justify-content: center;
			gap: 14px;
			height: auto;
			min-height: 52px;
			flex-wrap: wrap;
			box-sizing: border-box;
		}
		.toolbar_label { font: 600 13px var(--dhx-font); color: var(--dhx-ink-2); }
		.constraint_badge {
			display: inline-flex;
			align-items: center;
			gap: 5px;
			padding: 3px 7px;
			border-radius: 999px;
			background: rgba(37,99,235,.09);
			color: #1d4ed8;
			font: 600 11px var(--dhx-font);
			white-space: nowrap;
		}
		.constraint_badge--exact {
			background: rgba(229,72,77,.10);
			color: #c92f35;
		}
		:root[data-gantt-theme="dark"] .constraint_badge {
			background: rgba(59,130,246,.16);
			color: #9fc4ff;
		}
		:root[data-gantt-theme="dark"] .constraint_badge--exact {
			background: rgba(255,107,110,.16);
			color: #ff6b6e;
		}
		.constraint_cell { display: flex; align-items: center; justify-content: center; min-height: 100%; }
		.footer_note { margin-left: auto; color: var(--dhx-muted); }

		@media (max-width: 760px) {
			.schedule_header > .toolbar_label { flex: 1 1 100%; text-align: center; }
			.footer_note { display: none; }
		}
	</style>
</head>
<body>

<div class="dhx_header schedule_header">
	<span class="toolbar_label">Plan the project</span>
	<div class="dhx_seg" id="dir_seg">
		<button class="dhx_btn active" data-dir="forward" onclick="applyDirection('forward')">From the start</button>
		<button class="dhx_btn" data-dir="backward" onclick="applyDirection('backward')">From the deadline</button>
	</div>
	<span class="toolbar_label">Task constraints</span>
	<div class="dhx_seg" id="constraint_seg">
		<button class="dhx_btn" data-constraints="off" onclick="setConstraints(false)">Ignore</button>
		<button class="dhx_btn active" data-constraints="on" onclick="setConstraints(true)">Apply</button>
	</div>
</div>

<div id="gantt_here"></div>

<div class="dhx_footer">
	<span id="direction_status">Forward planning · constraints applied</span>
	<span class="footer_note">Constraint markers remain visible when their effect is disabled.</span>
</div>


</body>
</html>