Customizing printing — GanttChartView (JavaScript + Angular, React, Vue)

DlhSoft
Gantt chart libraries
6 min readMar 31, 2024

It’s time to continue our Gantt chart printing series with the JavaScript “edition”, after we’ve also discussed this matter in regard to our WPF components before.

As you may know, our JavaScript based components, such as GanttChartView and ScheduleChartView and so on, all come with an easy way to print their output — their built-in print function:

ganttChartView.print({ 
title: 'Gantt Chart Document',
isGridVisible: true,
columnIndexes: [1],
timelineStart: new Date(year, month, 1),
timelineFinish: new Date(new Date(year, month, 1).valueOf() + 5 * 7 * 24 * 60 * 60 * 1000),
preparingMessage: '...' });

This function will actually export the current content shown by the component at the time of calling, respecting the specified options. The list of options and their meanings is below:

  • title: The title to use for the temporary client window generated to support the print operation;
  • preparingMessage: The temporary message to be displayed in the output window while the asynchronous export operation is in progress;
  • isGridVisible: Determines whether the grid is exported; by default the value from the component instance settings is used;
  • columnIndexes: Determines the columns to be exported, considering the specified array of column indexes in the columns collection; by default all displayed columns are exported or printed;
  • timelineStart, timelineFinish: Start and finish date and times of the exported chart area; by default the values from the component instance settings are used;
  • hourWidth: Indicates the zoom level to be used for the exported chart area, and represents the actual number of device units each hour in the timeline gets available; to zoom in when exporting or printing, increase this value; to zoom out, decrease it; by default the value from the component instance settings is used;
  • startRowIndex, endRowIndex: Determines the rows to be exported, considering the specified indexes in the item collection; by default all rows, including those that are not currently visible in the viewport, are exported or printed;
  • rotate: Determines whether to rotate the exported content in the output document to simulate landscape printing;
  • autoClose: Determines whether to automatically close the temporary export output window generated for printing when the operation is completed.

Note that you can also call exportContent instead of print function to extract the content in printable form into a separate window, optionally passed as second argument of that call — exportContent({...}, output). If you pass an existing window, it will just “copy-paste” the content there, and you can manually “print” the content of it yourself.

(In reality, what the initially discussed print function does, is exactly that: exports the content into a temporary window, and then calls browser’s window.print method on it.)

Given this information above, let’s now build a custom Gantt chart print feature for our JavaScript application. We’ll have some kind of a “dialog” popping in when pressing the Print button in the toolbar, allowing the end user to select a timeline to print, and also the grid columns to include in the output. (Note that actual printing depends on the browser’s implementation, of course.)

First, we’ll have to define a “dialog” for printing options. One that can be specified directly in HTML, as a classic div, and then adjusted from JavaScript, of course:

<div id="printingOptionsDialog" class="editor">
<p class="header">Printing Options</p>
<table cellpadding="0" cellspacing="0">
<tr>
<td colspan="2"><p style="margin-top: 0; margin-bottom: 8px;">TIMELINE</p></td>
</tr>
<tr>
<td>Start date:</td>
<td class="cell"><input id="startDateTimelineEditor" /></td>
</tr>
<tr>
<td>Finish date:</td>
<td class="cell"><input id="finishDateTimelineEditor" /></td>
</tr>
<tr>
<td colspan="2"><div style="color: grey; margin-top: 4px; margin-bottom: 16px;">Note: printing output will include entire weeks.</div></td>
</tr>
<tr>
<td colspan="2"><p style="margin-top: 0; margin-bottom: 8px;">SELECT COLUMNS</p></td>
</tr>
<tr>
<td colspan="2"><div id="columnsList" style="border: 1px solid #aaa; height: 160px; width: 100%; overflow-y: auto;"></div></td>
</tr>
</table>
<div class="command-area">
<a class="command main" href="javascript:print()">Print</a>
<a class="command" href="javascript:close()">Close</a>
</div>
</div>
var DatePicker = DlhSoft.Controls.DatePicker;
var printingOptionsDialog = document.getElementById('printingOptionsDialog');
var startInput = document.getElementById('startDateTimelineEditor');
var finishInput = document.getElementById('finishDateTimelineEditor');
var columnsList = document.getElementById('columnsList');
var columnsDivCreated = false;
function openPrintingOptionsDialog() {
DatePicker.initialize(startInput, DlhSoft.Controls.GanttChartView.getOutputDate(ganttChartView.getProjectStart()), { defaultTimeOfDay: 8 * 60 * 60 * 1000 });
DatePicker.initialize(finishInput, DlhSoft.Controls.GanttChartView.getOutputDate(ganttChartView.getProjectFinish()), { defaultTimeOfDay: 16 * 60 * 60 * 1000 });
if (!columnsDivCreated) {
for (var i = 0; i < settings.columns.length; i++) {
var column = settings.columns[i];
var div = document.createElement("div");
div.style = "margin: 6px;";
columnsList.appendChild(div);
var cb = document.createElement("input");
cb.setAttribute("type", "checkbox");
cb.setAttribute("id", "columnCheckBox_" + i);
cb.setAttribute("style", "margin-right: 4px;");
cb.setAttribute("value", column.header);
cb.checked = true;
div.appendChild(cb);
var label = document.createElement("label");
var txt = document.createTextNode(column.header);
label.setAttribute("for", column.Header);
label.appendChild(txt);
div.appendChild(label);
}
columnsDivCreated = true;
}
printingOptionsDialog.style.display = 'block';
}
function close() {
printingOptionsDialog.style.display = 'none';
}

As you can see, here we use our own DatePicker component for allowing the user to pick the timeline to print (it comes licensed together with Gantt Chart Hyper Library, by the way), and a dynamic set of checkboxes as column selectors.

We ensure the style of printingOptionsDialog is initially set to display: none from CSS, of course (together with other settings):

.editor {
z-index: 1;
position: absolute;
left: 24px;
top: 32px;
border: 1px solid #808080;
padding: 10px;
background-color: white;
font-family: Tahoma, Arial;
font-size: 12px;
box-shadow: #808080 4px 4px;
display: none;
}

.editor .header {
margin-top: 0px;
font-size: larger;
font-weight: bold;
}

.editor .cell {
padding: 4px;
}

.editor .command-area {
margin-top: 8px;
}

.editor input {
border: 1px solid #e0e0e0;
padding: 4px;
}

.editor .command-area {
margin-top: 12px;
margin-bottom: 4px;
}

.editor .command-area .command {
text-decoration: none;
margin-right: 4px;
}

.editor .command-area .main {
font-weight: bold;
}

Finally, let’s see how we map the options from the dialog to the actual print function call at component level.

We’ll extract and validate the timeline input values, we’ll determine which columns are to be printed, then we’ll also validate the size of the printed output — using a getEffort call too with a specially computed ‘visibility schedule’ (to avoid issues with too large output documents), and finally, if everything is right, we call the real print method, with the mapped arguments, as needed.

Note that we also limit the items printed to a range computed based on the specified timeline. Also note, that the component will also expand the timeline to display entire weeks, and we display this in a label for end users too, to avoid confusion (directly defined in HTML above):

var printingThreshold = 12000000;
function print() {
// Determine timeline to be printed.
var timelineStart = new Date(startInput.value);
var timelineFinish = new Date(finishInput.value);
if (timelineStart > timelineFinish) {
alert('The selected dates are incorrect. Please choose a valid timeline.');
return;
}
// Determine columns to be printed (having their checkboxes checked).
var listOfColumnIndexes = [];
var gridWidth = 0;
for (var i = 0; i < settings.columns.length; i++) {
var cb = document.getElementById('columnCheckBox_' + i);
if (cb.checked == true) {
listOfColumnIndexes.push(i);
gridWidth += settings.columns[i].width;
}
}
// Find items range to be printed.
var minIndex = null, maxIndex = null;
for (var i = 0; i < ganttChartView.items.length; i++) {
var item = ganttChartView.items[i];
if (item.finish < timelineStart || item.start > timelineFinish)
continue;
if (minIndex == null)
minIndex = i;
maxIndex = i;
}
// Ensure size of printed output is below a certain threshold.
var itemsCount = maxIndex - minIndex + 1;
var timelineHours = DlhSoft.Controls.GanttChartView.getEffort(
timelineStart, timelineFinish,
{
workingWeekStart: ganttChartView.settings.visibleWeekStart, workingWeekFinish: ganttChartView.settings.visibleWeekFinish,
visibleDayStart: ganttChartView.settings.visibleDayStart, visibleDayFinish: ganttChartView.settings.visibleDayFinish
}) / (60 * 60 * 1000);
if (itemsCount * settings.itemHeight * (gridWidth + timelineHours * settings.hourWidth) > printingThreshold) {
alert('The printed output would be too big. Please select a shorter timeline and/or fewer columns.');
return;
}
// Actually export and print content.
ganttChartView.print({ title: 'Gantt Chart (printable)', isGridVisible: true, columnIndexes: listOfColumnIndexes, startRowIndex: minIndex, endRowIndex: maxIndex, timelineStart: timelineStart, timelineFinish: timelineFinish, preparingMessage: '...' });
printingOptionsDialog.style.display = 'none';
}

We hope it helps.

By the way, here is a Printing sample app with the setup defined in this article, with full source code, if you want to check it out and/or adapt it into your project. Enjoy!

--

--