Popular features/Export to PDF, PNG, and print
Loading live demo…from dhtmlxcode.com
Popular features
Export to PDF, PNG, and print
Export the live chart to PDF or PNG through a page-setup panel, or print it with a stylesheet that hides the demo chrome and renders every row.
gantt.plugins({ export_api: true, tooltip: true });
gantt.config.date_format = "%Y-%m-%d";
gantt.config.auto_types = true;
gantt.config.open_tree_initially = true;
gantt.config.grid_resize = true;
gantt.config.row_height = 34;
gantt.config.bar_height = 22;
/* ---------------------------------------------------------------------------
* DATASET
* ------------------------------------------------------------------------ */
let demoData = {
tasks: [
{ id: 1, text: "Q3 delivery plan", type: "project", open: true },
{ id: 10, text: "Platform", type: "project", parent: 1, open: true },
{ id: 11, text: "Migrate to new auth service", parent: 10, start_date: "2026-07-06", duration: 10, progress: 1 },
{ id: 12, text: "API gateway rollout", parent: 10, start_date: "2026-07-20", duration: 8, progress: 0.6 },
{ id: 13, text: "Observability upgrade", parent: 10, start_date: "2026-07-30", duration: 7, progress: 0.3 },
{ id: 14, text: "Database sharding", parent: 10, start_date: "2026-07-06", duration: 12, progress: 1 },
{ id: 15, text: "Rate limiting rollout", parent: 10, start_date: "2026-07-22", duration: 6, progress: 0.4 },
{ id: 16, text: "Secrets vault migration", parent: 10, start_date: "2026-08-04", duration: 5, progress: 0 },
{ id: 17, text: "CI/CD pipeline hardening", parent: 10, start_date: "2026-08-11", duration: 8, progress: 0 },
{ id: 20, text: "Product", type: "project", parent: 1, open: true },
{ id: 21, text: "Onboarding redesign", parent: 20, start_date: "2026-07-13", duration: 9, progress: 0.7 },
{ id: 22, text: "Billing self-service", parent: 20, start_date: "2026-07-27", duration: 12, progress: 0.2 },
{ id: 23, text: "Mobile parity", parent: 20, start_date: "2026-08-12", duration: 10, progress: 0 },
{ id: 25, text: "Search revamp", parent: 20, start_date: "2026-07-09", duration: 8, progress: 1 },
{ id: 26, text: "Notifications center", parent: 20, start_date: "2026-07-24", duration: 9, progress: 0.5 },
{ id: 27, text: "Accessibility pass", parent: 20, start_date: "2026-08-06", duration: 6, progress: 0.3 },
{ id: 28, text: "In-app pricing page", parent: 20, start_date: "2026-08-17", duration: 7, progress: 0 },
{ id: 24, text: "Public beta", parent: 20, type: "milestone", start_date: "2026-08-28", progress: 0 },
{ id: 30, text: "Go-to-market", type: "project", parent: 1, open: true },
{ id: 31, text: "Launch content", parent: 30, start_date: "2026-08-17", duration: 8, progress: 0 },
{ id: 32, text: "Sales enablement", parent: 30, start_date: "2026-08-24", duration: 6, progress: 0 },
{ id: 34, text: "Analyst briefings", parent: 30, start_date: "2026-07-27", duration: 7, progress: 0.6 },
{ id: 35, text: "Partner co-marketing", parent: 30, start_date: "2026-08-10", duration: 9, progress: 0.2 },
{ id: 36, text: "Press kit and embargo plan", parent: 30, start_date: "2026-08-19", duration: 5, progress: 0 },
{ id: 37, text: "Launch event logistics", parent: 30, start_date: "2026-08-26", duration: 6, progress: 0 },
{ id: 33, text: "Q3 launch", parent: 30, type: "milestone", start_date: "2026-09-04", progress: 0 }
],
links: [
{ id: 1, source: 11, target: 12, type: "0" }, { id: 2, source: 12, target: 13, type: "0" },
{ id: 3, source: 21, target: 22, type: "0" }, { id: 4, source: 22, target: 23, type: "0" },
{ id: 5, source: 23, target: 24, type: "0" }, { id: 6, source: 13, target: 31, type: "0" },
{ id: 7, source: 24, target: 31, type: "0" }, { id: 8, source: 31, target: 32, type: "0" },
{ id: 9, source: 32, target: 33, type: "0" }, { id: 10, source: 14, target: 15, type: "0" },
{ id: 11, source: 15, target: 16, type: "0" }, { id: 12, source: 16, target: 17, type: "0" },
{ id: 13, source: 25, target: 26, type: "0" }, { id: 14, source: 26, target: 27, type: "0" },
{ id: 15, source: 27, target: 28, type: "0" }, { id: 16, source: 34, target: 35, type: "0" },
{ id: 17, source: 35, target: 36, type: "0" }, { id: 18, source: 36, target: 37, type: "0" }
]
};
gantt.config.columns = [
{ name: "text", label: "Task", tree: true, width: 240, resize: true, template: function (t) { return "<b>" + gantt.getWBSCode(t) + "</b> " + t.text; } },
{ name: "start_date", label: "Start", align: "center", width: 100, resize: true },
{ name: "duration", label: "Days", align: "center", width: 56, resize: true },
{ name: "add", width: 40, resize: true }
];
gantt.config.grid_width = 430;
gantt.templates.tooltip_text = function (start, end, task) {
return "<b>" + task.text + "</b><br>" + Math.round((task.progress || 0) * 100) + "% complete";
};
/* ---------------------------------------------------------------------------
* ZOOM
* ------------------------------------------------------------------------ */
gantt.config.scale_height = 50;
gantt.config.scales = [
{ unit: "month", step: 1, format: "%F %Y" },
{ unit: "week", step: 1, format: function (d) {
return gantt.date.date_to_str("%d")(d);
}
}];
let zoomNames = ["week", "month"];
gantt.ext.zoom.init({
levels: [
{ name: "week", scale_height: 50, scales: [{ unit: "month", step: 1, format: "%F %Y" }, { unit: "week", step: 1, format: function (d) { return "Wk " + gantt.date.date_to_str("%W")(d); } }] },
{ name: "month", scale_height: 50, scales: [{ unit: "year", step: 1, format: "%Y" }, { unit: "month", step: 1, format: "%M" }] }
],
element: function () { return gantt.$root.querySelector(".gantt_task"); }
});
DHX.zoom.init(gantt, zoomNames, "zoom_seg", null);
function setZoom(name) { DHX.zoom.set(name); }
/* ---------------------------------------------------------------------------
* EXPORT OPTIONS
* ------------------------------------------------------------------------ */
let exportOpts = { format: "A4", landscape: true, fit: false, header: "Q3 delivery plan", footer: "Confidential" };
function setOpt(key, value, btn) {
exportOpts[key] = value;
let seg = btn.parentNode;
seg.querySelectorAll("button").forEach(function (b) { b.classList.remove("active"); });
btn.classList.add("active");
updateSummary();
}
function toggleFit() {
exportOpts.fit = !exportOpts.fit;
DHX.toggle.setById("fit_switch", exportOpts.fit);
updateSummary();
}
function updateSummary() {
DHX.ui.setText("opt_summary", exportOpts.format + " · " + (exportOpts.landscape ? "Landscape" : "Portrait") + (exportOpts.fit ? " · fit to page" : ""));
}
function buildSettings() {
let settings = {
header: exportOpts.header ? "<h3 style='font-family:Inter,Arial'>" + esc(exportOpts.header) + "</h3>" : "",
footer: exportOpts.footer ? "<div style='font-family:Inter,Arial;color:#888'>" + esc(exportOpts.footer) + "</div>" : "",
skin: darkTheme ? "dark" : "material",
additional_settings: { format: exportOpts.format, landscape: exportOpts.landscape }
};
if (exportOpts.fit) settings.additional_settings.fit = "page";
return settings;
}
function esc(t) { return String(t).replace(/[&<>"]/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; }); }
function doExport(type) {
let settings = buildSettings();
settings.name = "q3-delivery-plan." + type;
toast("Building " + type.toUpperCase() + " (" + exportOpts.format + ") ...");
if (type === "pdf") gantt.exportToPDF(settings);
else gantt.exportToPNG(settings);
}
/* ---------------------------------------------------------------------------
* PRINT
*
* Browsers do not reliably fragment the nested Gantt layout. In landscape
* mode Chromium may move the complete chart to page two and leave page one
* blank. To avoid that, the live chart is expanded with autosize="xy", cloned,
* and placed into explicit print-page slices.
* ------------------------------------------------------------------------ */
let printSession = null;
let printRestoreTimer = null;
let PAPER_SIZES_MM = {
A4: { width: 210, height: 297 },
A3: { width: 297, height: 420 },
Letter: { width: 215.9, height: 279.4 }
};
let PRINT_MARGIN_MM = 10;
function mmToPixels(value) {
return value * 96 / 25.4;
}
function getPrintPageSize() {
let paper = PAPER_SIZES_MM[exportOpts.format] || PAPER_SIZES_MM.A4;
let width = paper.width;
let height = paper.height;
if (exportOpts.landscape) {
let temporary = width;
width = height;
height = temporary;
}
return {
width: width,
height: height,
printableWidth: Math.max(1, width - PRINT_MARGIN_MM * 2),
printableHeight: Math.max(1, height - PRINT_MARGIN_MM * 2)
};
}
function updatePrintPageRule() {
let style = document.getElementById("native_print_page_style");
if (!style) {
style = document.createElement("style");
style.id = "native_print_page_style";
document.head.appendChild(style);
}
style.textContent =
"@page { size: " + exportOpts.format + " " +
(exportOpts.landscape ? "landscape" : "portrait") +
"; margin: " + PRINT_MARGIN_MM + "mm; }";
}
function removePrintPages() {
let pages = document.getElementById("gantt_print_pages");
if (pages && pages.parentNode) {
pages.parentNode.removeChild(pages);
}
document.body.classList.remove("print_pages_ready");
}
function restorePrintLayout() {
if (!printSession) return;
clearTimeout(printRestoreTimer);
let session = printSession;
let ganttNode = document.getElementById("gantt_here");
printSession = null;
removePrintPages();
gantt.config.autosize = session.autosize;
gantt.config.static_background = session.staticBackground;
ganttNode.style.cssText = session.containerStyle;
document.body.classList.remove("print_preparing");
let pageStyle = document.getElementById("native_print_page_style");
if (pageStyle) pageStyle.textContent = "";
gantt.render();
requestAnimationFrame(function () {
gantt.scrollTo(session.scroll.x, session.scroll.y);
window.scrollTo(session.windowScroll.x, session.windowScroll.y);
});
}
function calculatePrintScale(fullWidth, fullHeight) {
let page = getPrintPageSize();
let widthScale = mmToPixels(page.printableWidth) / fullWidth;
if (!exportOpts.fit) {
return Math.min(1, widthScale);
}
let heightScale = mmToPixels(page.printableHeight) / fullHeight;
return Math.min(1, widthScale, heightScale);
}
function cloneExpandedGantt(ganttNode, fullWidth, fullHeight) {
let clone = ganttNode.cloneNode(true);
clone.removeAttribute("id");
clone.classList.add("gantt_print_clone");
clone.style.cssText = ganttNode.style.cssText;
clone.style.width = fullWidth + "px";
clone.style.height = fullHeight + "px";
clone.style.maxWidth = "none";
clone.style.maxHeight = "none";
clone.style.flex = "none";
clone.style.zoom = "";
return clone;
}
function buildPrintPages(ganttNode, fullWidth, fullHeight, scale) {
removePrintPages();
let page = getPrintPageSize();
let printableWidthPx = mmToPixels(page.printableWidth);
let printableHeightPx = mmToPixels(page.printableHeight);
let scaledHeight = fullHeight * scale;
let pageCount = exportOpts.fit
? 1
: Math.max(1, Math.ceil(scaledHeight / printableHeightPx));
let pages = document.createElement("div");
pages.id = "gantt_print_pages";
pages.className = "gantt_print_pages";
for (let pageIndex = 0; pageIndex < pageCount; pageIndex++) {
let pageNode = document.createElement("section");
pageNode.className = "gantt_print_page";
pageNode.style.width = page.printableWidth + "mm";
pageNode.style.height = page.printableHeight + "mm";
let scaled = document.createElement("div");
scaled.className = "gantt_print_scaled";
scaled.style.top = -(pageIndex * printableHeightPx) + "px";
scaled.style.width = fullWidth + "px";
scaled.style.height = fullHeight + "px";
scaled.style.transform = "scale(" + scale + ")";
scaled.appendChild(cloneExpandedGantt(ganttNode, fullWidth, fullHeight));
pageNode.appendChild(scaled);
pages.appendChild(pageNode);
}
document.body.appendChild(pages);
document.body.classList.add("print_pages_ready");
return {
pageCount: pageCount,
scale: scale,
printableWidthPx: printableWidthPx,
printableHeightPx: printableHeightPx
};
}
function preparePrintLayout() {
let ganttNode = document.getElementById("gantt_here");
printSession = {
autosize: gantt.config.autosize,
staticBackground: gantt.config.static_background,
containerStyle: ganttNode.style.cssText,
scroll: gantt.getScrollState(),
windowScroll: { x: window.scrollX, y: window.scrollY }
};
document.body.classList.add("print_preparing");
updatePrintPageRule();
gantt.config.autosize = "xy";
gantt.config.static_background = false;
ganttNode.style.flex = "none";
ganttNode.style.minHeight = "0";
ganttNode.style.maxHeight = "none";
ganttNode.style.maxWidth = "none";
ganttNode.style.zoom = "";
gantt.render();
return new Promise(function (resolve) {
requestAnimationFrame(function () {
requestAnimationFrame(function () {
let fullWidth = Math.max(
ganttNode.scrollWidth,
ganttNode.offsetWidth,
ganttNode.getBoundingClientRect().width
);
let fullHeight = Math.max(
ganttNode.scrollHeight,
ganttNode.offsetHeight,
ganttNode.getBoundingClientRect().height
);
let scale = calculatePrintScale(fullWidth, fullHeight);
let result = buildPrintPages(ganttNode, fullWidth, fullHeight, scale);
if (scale < 0.55) {
toast(
"Print scale is " + Math.round(scale * 100) +
"%. A3 or PDF export will be easier to read."
);
} else if (result.pageCount > 1) {
toast("Preparing " + result.pageCount + " print pages...");
}
resolve();
});
});
});
}
function doPrint() {
if (printSession) return;
preparePrintLayout().then(function () {
window.addEventListener("afterprint", restorePrintLayout, { once: true });
requestAnimationFrame(function () {
document.body.classList.remove("print_preparing");
window.print();
/* Fallback for browsers where afterprint is delayed or omitted. */
setTimeout(restorePrintLayout, 0);
});
});
printRestoreTimer = setTimeout(restorePrintLayout, 120000);
}
/* ---------------------------------------------------------------------------
* THEME + TOAST
* ------------------------------------------------------------------------ */
let darkTheme = false;
function toggleTheme() {
darkTheme = !darkTheme;
document.getElementById("theme_sun").classList.toggle("dhx_hidden", darkTheme);
document.getElementById("theme_moon").classList.toggle("dhx_hidden", !darkTheme);
gantt.setSkin(darkTheme ? "dark" : "");
gantt.render();
}
let toastTimer = null;
function toast(msg) {
let el = document.getElementById("toast");
el.textContent = msg;
el.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { el.classList.remove("show"); }, 2600);
}
/* ---------------------------------------------------------------------------
* INIT
* ------------------------------------------------------------------------ */
DHX.dd.initClickOutside();
gantt.init("gantt_here");
gantt.parse(demoData);
DHX.zoom.set("week");
updateSummary();<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Export to PDF / PNG & print: 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 { width: 100%; flex: 1 1 auto; min-height: 0; }
.opt_menu { min-width: 230px; padding: 12px; }
.opt_row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 7px 0; font: 500 13px var(--dhx-font); color: var(--dhx-ink-2); }
.opt_row + .opt_row { border-top: 1px solid var(--dhx-line-2); }
.opt_seg { display: inline-flex; background: #eef1f4; border-radius: 8px; padding: 2px; }
.opt_seg button { border: none; background: none; cursor: pointer; height: 26px; padding: 0 10px; border-radius: 6px; font: 600 12px var(--dhx-font); color: var(--dhx-ink-2); }
.opt_seg button.active { background: #fff; color: var(--dhx-ink); box-shadow: 0 1px 2px rgba(16,24,40,.12); }
:root[data-gantt-theme="dark"] .opt_seg { background: #242a33; }
:root[data-gantt-theme="dark"] .opt_seg button.active { background: #323a45; color: #fff; }
.opt_text { width: 130px; height: 30px; border: 1px solid var(--dhx-line); border-radius: 7px; padding: 0 9px; font: 500 13px var(--dhx-font); background: var(--dhx-surface); color: var(--dhx-ink); }
.note { font: 500 12px var(--dhx-font); color: var(--dhx-muted); }
.toast {
position: fixed; bottom: 48px; left: 50%; transform: translateX(-50%) translateY(20px);
background: #1f2630; color: #fff; padding: 10px 16px; border-radius: 10px;
font: 500 13px var(--dhx-font); box-shadow: 0 8px 24px rgba(16,24,40,.3);
opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s; z-index: 80;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
body.print_preparing {
cursor: progress;
}
.gantt_print_pages {
display: none;
}
@media print {
.dhx_header, .dhx_toolbar, .dhx_footer, .toast,
body.print_pages_ready > #gantt_here {
display: none !important;
}
html, body {
display: block !important;
overflow: visible !important;
width: auto !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
}
.gantt_print_pages {
display: block !important;
width: 100%;
margin: 0;
padding: 0;
}
.gantt_print_page {
position: relative;
overflow: hidden;
margin: 0;
padding: 0;
break-after: page;
page-break-after: always;
}
.gantt_print_page:last-child {
break-after: auto;
page-break-after: auto;
}
.gantt_print_scaled {
position: absolute;
left: 0;
transform-origin: top left;
}
.gantt_print_clone {
display: block !important;
flex: none !important;
overflow: visible !important;
margin: 0 !important;
}
.gantt_print_clone .gantt_container,
.gantt_print_clone .gantt_layout_root,
.gantt_print_clone .gantt_layout_cell,
.gantt_print_clone .gantt_task,
.gantt_print_clone .gantt_grid {
overflow: visible !important;
}
.gantt_task_line, .gantt_line_wrapper > div {
print-color-adjust: exact;
}
}
</style>
</head>
<body>
<!-- Header -->
<div class="dhx_header">
<div class="dhx_brand dhx_brand--static">
<button class="dhx_brand__btn">
<div class="dhx_brand__logo" style="background:linear-gradient(140deg,#ef4444,#f97316)">PD</div>
<div class="dhx_brand__titles">
<div class="dhx_brand__title-row"><span class="dhx_brand__title">Q3 delivery plan</span></div>
<div class="dhx_brand__meta"><span class="dhx_brand__subtitle">Export & print</span></div>
</div>
</button>
</div>
<div class="dhx_header__center">
<div class="dhx_seg" id="zoom_seg">
<button class="dhx_btn" data-zoom="week" onclick="setZoom('week')">Week</button>
<button class="dhx_btn" data-zoom="month" onclick="setZoom('month')">Month</button>
</div>
</div>
<div class="dhx_header__right">
<button class="dhx_btn dhx_btn--icon" title="Toggle theme" onclick="toggleTheme()">
<svg class="dhx_ic" id="theme_sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4.2"/><line x1="12" y1="2.5" x2="12" y2="5"/><line x1="12" y1="19" x2="12" y2="21.5"/><line x1="2.5" y1="12" x2="5" y2="12"/><line x1="19" y1="12" x2="21.5" y2="12"/><line x1="5.3" y1="5.3" x2="7" y2="7"/><line x1="17" y1="17" x2="18.7" y2="18.7"/><line x1="5.3" y1="18.7" x2="7" y2="17"/><line x1="17" y1="7" x2="18.7" y2="5.3"/></svg>
<svg class="dhx_ic dhx_hidden" id="theme_moon" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4a7 7 0 1 0 10.5 10.5z"/></svg>
</button>
</div>
</div>
<!-- Toolbar -->
<div class="dhx_toolbar">
<div class="dhx_dd" id="dd_page">
<button class="dhx_btn dhx_btn--surface" onclick="DHX.dd.toggle('dd_page')">
Page setup <span class="dhx_caret"><svg class="dhx_ic dhx_ic--sm" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></span>
</button>
<div class="dhx_menu opt_menu" id="page_menu">
<div class="opt_row">Page size
<span class="opt_seg" id="seg_format">
<button class="active" data-format="A4" onclick="setOpt('format','A4',this)">A4</button>
<button data-format="A3" onclick="setOpt('format','A3',this)">A3</button>
<button data-format="Letter" onclick="setOpt('format','Letter',this)">Letter</button>
</span>
</div>
<div class="opt_row">Orientation
<span class="opt_seg" id="seg_orient">
<button class="active" data-landscape="true" onclick="setOpt('landscape',true,this)">Landscape</button>
<button data-landscape="false" onclick="setOpt('landscape',false,this)">Portrait</button>
</span>
</div>
<div class="opt_row">Fit to one page
<label class="dhx_toggle" style="height:30px" onclick="toggleFit()">
<span class="dhx_toggle__track" id="fit_switch"></span>
</label>
</div>
<div class="opt_row">Header
<input class="opt_text" id="hdr_text" value="Q3 delivery plan" oninput="exportOpts.header = this.value">
</div>
<div class="opt_row">Footer
<input class="opt_text" id="ftr_text" value="Confidential" oninput="exportOpts.footer = this.value">
</div>
</div>
</div>
<button class="dhx_btn dhx_btn--primary" onclick="doExport('pdf')">
<svg class="dhx_ic" viewBox="0 0 24 24"><path d="M12 3v12"/><polyline points="8 11 12 15 16 11"/><path d="M5 19h14"/></svg>
Export PDF
</button>
<button class="dhx_btn dhx_btn--surface" onclick="doExport('png')">Export PNG</button>
<button class="dhx_btn dhx_btn--surface" onclick="doPrint()">
<svg class="dhx_ic" viewBox="0 0 24 24"><polyline points="6 9 6 3 18 3 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="7"/></svg>
Print
</button>
<div class="dhx_spacer"></div>
<span class="note" id="opt_summary"></span>
</div>
<div id="gantt_here"></div>
<div class="toast" id="toast"></div>
</body>
</html>