Popular features/Zoom to fit
Loading live demo…from dhtmlxcode.com
Popular features
Zoom to fit
Step the zoom level in and out or zoom to fit the project's own start and end markers, on a timeline anchored to today's date.
gantt.plugins({ marker: true });
gantt.config.date_format = "%Y-%m-%d";
gantt.config.row_height = 34;
gantt.config.bar_height = 22;
gantt.config.fit_tasks = true;
function addDays(base, n) {
let d = gantt.date.day_start(new Date(base));
return gantt.date.add(d, n, "day");
}
let TODAY = gantt.date.day_start(new Date());
/* Project bounds, flagged on the timeline below and used as the explicit
fit range: Zoom to fit targets these markers rather than scanning
every task for its min/max date. */
let PROJECT_START = addDays(TODAY, -198);
let PROJECT_END = addDays(TODAY, 380);
/* The chart boundaries are widened on demand (see zoomOut() below) rather
than left permanently padded: see the comment on zoomFit() for why. */
function widenRange() {
gantt.config.start_date = addDays(TODAY, -365 * 3);
gantt.config.end_date = addDays(TODAY, 365 * 2);
}
widenRange();
let weekScale = { unit: "week", step: 1, format: function (date) {
let toStr = gantt.date.date_to_str("%d %M");
let end = gantt.date.add(date, 6, "day");
return toStr(date) + " - " + toStr(end);
}};
let zoomConfig = {
levels: [
{ name: "day", scale_height: 50, scales: [
{ unit: "month", format: "%F %Y" },
{ unit: "day", step: 1, format: "%j %D" }
] },
{ name: "week", scale_height: 50, scales: [
{ unit: "month", format: "%F %Y" },
weekScale
] },
{ name: "month", scale_height: 50, scales: [
{ unit: "year", format: "%Y" },
{ unit: "month", format: "%M" }
] },
{ name: "quarter", scale_height: 50, scales: [
{ unit: "year", format: "%Y" },
{ unit: "quarter", step: 1, format: function (date) { return "Q" + (Math.floor(date.getMonth() / 3) + 1); } }
] },
{ name: "year", scale_height: 50, scales: [
{ unit: "year", step: 1, format: "%Y" }
] }
],
useKey: "ctrlKey",
trigger: "wheel",
element: function () { return gantt.$root.querySelector(".gantt_task"); }
};
gantt.config.columns = [
{ name: "text", label: "Workstream", tree: true, width: 240, resize: true },
{ name: "start_date", label: "Start", align: "center", width: 100, resize: true },
{ name: "duration", label: "Days", align: "center", width: 58, resize: true },
{ name: "add", width: 40, resize: true }
];
gantt.ext.zoom.init(zoomConfig);
gantt.ext.zoom.setLevel("month");
/* No segmented control or scale label: only zoomIn/zoomOut/zoomFit drive the scale. */
DHX.zoom.init(gantt, ["day", "week", "month", "quarter", "year"], null, null);
function zoomFit() {
/* rangeMode: "target" forces zoomToFit to actually shrink start_date/
end_date down to the Project start/end flags, so the fit is tight on
any viewport width. (Leaving it on the default "preserve", which
kicks in automatically once start_date/end_date are set, only picks
a scale while leaving the wide range rendered underneath, so wider
windows show more of that empty padding instead of a tighter fit). */
gantt.ext.zoom.zoomToFit({
range: { start_date: PROJECT_START, end_date: PROJECT_END },
rangeMode: "target"
});
DHX.zoom.syncUI();
}
function zoomOut() {
/* Re-widen the range before stepping out, since zoomFit() (and a plain
render) may have narrowed it back down to the project's own span. */
widenRange();
gantt.ext.zoom.zoomOut();
DHX.zoom.syncUI();
}
/* data: same multi-region program as 102_zoom_to_fit.html, but every
date is an offset (in days) from TODAY instead of a fixed calendar date,
so "today" always lands mid-EMEA-rollout like the original progress
values (0.6 / 0.3 / ...) imply. */
let data = {
tasks: [
{ id: 1, text: "ERP Rollout", type: "project", open: true, progress: 0.3 },
{ id: 10, text: "Foundation", type: "project", parent: 1, open: true },
{ id: 11, text: "Vendor selection", parent: 10, start_date: PROJECT_START, duration: 30, progress: 1 },
{ id: 12, text: "Global template", parent: 10, start_date: addDays(TODAY, -156), duration: 55, progress: 0.9 },
{ id: 13, text: "Data model design", parent: 10, start_date: addDays(TODAY, -114), duration: 40, progress: 0.7 },
{ id: 14, text: "Security review", parent: 10, start_date: addDays(TODAY, -95), duration: 25, progress: 0.6 },
{ id: 20, text: "Region: EMEA", type: "project", parent: 1, open: true },
{ id: 21, text: "Configuration", parent: 20, start_date: addDays(TODAY, -73), duration: 45, progress: 0.6 },
{ id: 22, text: "Data migration", parent: 20, start_date: addDays(TODAY, -10), duration: 35, progress: 0.3 },
{ id: 24, text: "User acceptance testing", parent: 20, start_date: addDays(TODAY, 26), duration: 12, progress: 0.1 },
{ id: 23, text: "EMEA go-live", parent: 20, type: "milestone", start_date: addDays(TODAY, 39) },
{ id: 30, text: "Region: Americas", type: "project", parent: 1, open: true },
{ id: 31, text: "Configuration", parent: 30, start_date: addDays(TODAY, 39), duration: 45, progress: 0.2 },
{ id: 32, text: "Data migration", parent: 30, start_date: addDays(TODAY, 102), duration: 35, progress: 0 },
{ id: 35, text: "Change management & training", parent: 30, start_date: addDays(TODAY, 39), duration: 90, progress: 0 },
{ id: 34, text: "User acceptance testing", parent: 30, start_date: addDays(TODAY, 138), duration: 12, progress: 0 },
{ id: 33, text: "Americas go-live", parent: 30, type: "milestone", start_date: addDays(TODAY, 151) },
{ id: 40, text: "Region: APAC", type: "project", parent: 1, open: true },
{ id: 41, text: "Configuration", parent: 40, start_date: addDays(TODAY, 165), duration: 45, progress: 0 },
{ id: 42, text: "Data migration", parent: 40, start_date: addDays(TODAY, 228), duration: 35, progress: 0 },
{ id: 44, text: "User acceptance testing", parent: 40, start_date: addDays(TODAY, 264), duration: 12, progress: 0 },
{ id: 43, text: "APAC go-live", parent: 40, type: "milestone", start_date: addDays(TODAY, 277) },
{ id: 45, text: "Region: LATAM", type: "project", parent: 1, open: true },
{ id: 46, text: "Configuration", parent: 45, start_date: addDays(TODAY, 290), duration: 40, progress: 0 },
{ id: 47, text: "Data migration", parent: 45, start_date: addDays(TODAY, 331), duration: 30, progress: 0 },
{ id: 48, text: "LATAM go-live", parent: 45, type: "milestone", start_date: addDays(TODAY, 365) },
{ id: 50, text: "Global close-out", parent: 1, type: "milestone", start_date: PROJECT_END }
],
links: [
{ id: 1, source: 11, target: 12, type: "0" },
{ id: 2, source: 12, target: 13, type: "0" },
{ id: 3, source: 13, target: 14, type: "0" },
{ id: 4, source: 14, target: 21, type: "0" },
{ id: 5, source: 21, target: 22, type: "0" },
{ id: 6, source: 22, target: 24, type: "0" },
{ id: 7, source: 24, target: 23, type: "0" },
{ id: 8, source: 23, target: 31, type: "0" },
{ id: 9, source: 31, target: 32, type: "0" },
{ id: 10, source: 32, target: 34, type: "0" },
{ id: 11, source: 34, target: 33, type: "0" },
{ id: 12, source: 33, target: 41, type: "0" },
{ id: 13, source: 41, target: 42, type: "0" },
{ id: 14, source: 42, target: 44, type: "0" },
{ id: 15, source: 44, target: 43, type: "0" },
{ id: 16, source: 43, target: 46, type: "0" },
{ id: 17, source: 46, target: 47, type: "0" },
{ id: 18, source: 47, target: 48, type: "0" },
{ id: 19, source: 48, target: 50, type: "0" }
]
};
gantt.init("gantt_here");
gantt.parse(data);
gantt.addMarker({ start_date: PROJECT_START, css: "project_flag", text: "Project start", title: "Project start" });
gantt.addMarker({ start_date: PROJECT_END, css: "project_flag", text: "Project end", title: "Project end" });<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Zoom & zoom-to-fit, anchored to today: 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; }
.dhx_header .dhx_btn { height: 36px; }
.dhx_stepper { gap: 7px; }
.dhx_stepper .dhx_btn--icon {
width: 36px;
min-width: 36px;
padding: 0;
border-radius: 8px;
}
.dhx_stepper .dhx_btn--icon .dhx_ic { width: 18px; height: 18px; }
.gantt_marker.project_flag { background: #f59e0b; }
.gantt_marker.project_flag .gantt_marker_content { background: #f59e0b; font-weight: 600; }
</style>
</head>
<body>
<div class="dhx_header dhx_header--centered">
<div class="dhx_stepper">
<button class="dhx_btn dhx_btn--icon" title="Zoom out" onclick="zoomOut()">
<svg class="dhx_ic" viewBox="0 0 16 16"><line x1="3" y1="8" x2="13" y2="8"/></svg>
</button>
<button class="dhx_btn dhx_btn--icon" title="Zoom in" onclick="DHX.zoom.step(1)">
<svg class="dhx_ic" viewBox="0 0 16 16"><line x1="8" y1="3" x2="8" y2="13"/><line x1="3" y1="8" x2="13" y2="8"/></svg>
</button>
</div>
<button class="dhx_btn dhx_btn--primary" onclick="zoomFit()">
<svg class="dhx_ic" viewBox="0 0 16 16" style="stroke:#fff"><path d="M2 6V2h4M14 6V2h-4M2 10v4h4M14 10v4h-4"/></svg>
Zoom to fit
</button>
</div>
<div id="gantt_here"></div>
</body>
</html>