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

Schedule direction

Plan 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>