Gantt charts, commonly used in project management, are one of the most popular and useful ways of showing activities (tasks or events) displayed against time or tracking project schedules.

We've had many requests for Gantt charts for awhile. At first, we thought we'd have to create a new chart type (like Bar, Column, etc) for this.

Gantt charts are essentially Bar charts where each bar shows a range rather than a single value.

But we quickly realized that Gantt charts are essentially Bar charts where each bar shows a range (start to finish) rather than a single value. So we decided to improve the existing Bar and Column chart types to allow them to show ranges, rather than single values. This was done simply by making the series binding property accept a comma-delimited list of two properties, rather than a single one. This is the same approach we already used with complex series types like Bubble, CandleStick, and HighLowOpenClose.

This small change significantly enhanced the power of the Bar and Column series, which now can be used to plot ranges instead of single values. And it also allowed us to create Gantt charts, which is what we'll focus on here.

Creating Gantt charts with the FlexChart control is simply a matter of creating a Bar chart, binding the x-axis to the time values, and adding a series bound to the start/end values. Once that's done, you can add detail to the Gantt chart using the standard formatItem property and the rendering and rendered events.

If you want to see an example right now, check out our GanttChart sample. Read on to learn how to do it.

Preparing the Data

The FlexChart control accepts data as arrays of objects. In this case, the objects are tasks with a name, start and end times. Our tasks also have two additional properties: the percent complete and the name of a parent task which must be completed before the current task can start.

Our sample data looks like this:

        function getAdvancedData() {
            var year = new Date().getFullYear();
            return [
                { 
                    name: 'Task1', 
                    start: new Date(year, 0, 1), 
                    end: new Date(year, 2, 31), 
                    parent: null, 
                    percent: 100
                },
                { 
                    name: 'Task2', 
                    start: new Date(year, 3, 1), 
                    end: new Date(year, 3, 30), 
                    parent: 'Task1', 
                    percent: 100
                },
                // ...
                { 
                    name: 'Task7', 
                    start: new Date(year, 0, 1), 
                    end: new Date(year, 11, 31), 
                    parent: null, 
                    percent: 50 
                }
            ];
        }

Creating the basic Gantt Chart

Once the data is ready, we can create the basic Gantt chart, which is simply a ranged bar FlexChart:

        new wijmo.chart.FlexChart('#gantt', {
            itemsSource: getAdvancedData(),
            chartType: 'Bar',
            bindingX: 'name',
            axisY: {
                majorGrid: false,
                minorGrid: true,
                reversed: true
            },
            series: [
                { binding: 'start,end' }
            ]
        });

The result looks like this:

This is a correct, but very minimalistic, Gantt chart. In the next step, we'll enhance it to add tooltips, percent complete indicators, and dependency lines.

Enhancing the Gantt Chart

Once the basic chart is done, we can enhance it in a few ways:

  • Add tooltips to show task details when the user moves the mouse over a given task,
  • Use the itemFormatter property to add elements showing the percentage completed for each task, and
  • Use the rendered event to add connecting lines showing the task dependencies.

To support these additions, let's start by extending the options used to create the chart:

        new wijmo.chart.FlexChart('#gantt', {
            itemsSource: getAdvancedData(),
            chartType: 'Bar',
            bindingX: 'name',
            axisY: {
                majorGrid: false,
                minorGrid: true,
                reversed: true
            },
            series: [
                { binding: 'start,end' }
            ],
            tooltip: {
                content: getTooltipContent
            },
            itemFormatter: ganttItemFormatter,
            rendered: ganttChartRendered,
        });

The getTooltipContent function is defined as follows:

        function getTooltipContent(ht) {
            var str = wijmo.format('<b>{name}</b>:<br/>{start:d} - {end:d}', {
                name: ht.x,
                start: ht.item.start,
                end: ht.item.end
            });
            if (ht.item && ht.item.percent != null) {
                str += wijmo.format(
                  '<br/><i>percent complete: {percent}%</i>', 
                   ht.item);
            }
            return str;
        }

The function receives a HitTestInfo parameter that identifies the task being hovered, and returns an HTML string with the task description.

Next, let's take a look at the itemFormatter function that adds an element to show the percentage of the task that has been completed:

        function ganttItemFormatter(engine, ht, defaultFormatter) {

            // draw the item as usual
            defaultFormatter();

            // show percentage done
            var task = ht.item;
            if (wijmo.isNumber(task.percent) && task.percent > 0) {
                var pct = wijmo.clamp(task.percent, 0, 100) / 100,
                    rc = getTaskRect(ht.series.chart, task).inflate(-8, -8);
                engine.fill = pct == 1 ? 'green' : 'gold';
                engine.drawRect(rc.left, rc.top, rc.width * pct, rc.height);
            }
        }

The code starts by calling the defaultFormatter callback parameter, which renders the chart element as usual.

Then it gets a reference to the task being plotted from the ht parameter and checks whether the tasks percent property is a number greater than zero. If so, it uses the engine.drawRect method to display a rectangle over the original element with a width that corresponds to the percentage of the task that has been completed.

The last missing piece is the code that draws connecting lines between parent and child tasks. We want the connecting lines to be drawn above the task bars, so we use the chart's rendered event to add those elements. Here is the code:

        function ganttChartRendered(s, e) {
            var chart = s,
                tasks = chart.collectionView.items;
            tasks.forEach(function (task) { // for each task
                var parents = getTaskParents(task, tasks); // get the parent tasks
                parents.forEach(function (parent) { // for each parent
                    drawConnectingLine(e.engine, chart, task, parent); // draw connector
                });
            });
        }
        function drawConnectingLine(engine, chart, task, parent) {
            var rc1 = getTaskRect(chart, parent),   // parent rect
                rc2 = getTaskRect(chart, task),     // task rect
                x1 = rc1.left + rc1.width / 2,      // parent x center
                x2 = rc2.left,                      // task left
                y1 = rc1.bottom,                    // parent bottom
                y2 = rc2.top + rc2.height / 2;      // task y center

            // draw connecting line
            var xs = [x1, x1, x2],
                ys = [y1, y2, y2];
            engine.drawLines(xs, ys, 'connector', {
                stroke: 'black'
            });

            // draw arrow at the end
            var sz = 5;
            xs = [x2 - 2 * sz, x2, x2 - 2 * sz];
            ys = [y2 - sz, y2, y2 + sz];
            engine.drawPolygon(xs, ys, 'arrow', {
                fill: 'black'
            })
        }

The code enumerates the tasks and uses the chart's engine to draw the connecting lines and arrows at the appropriate positions.

Both the ganttItemFormatter and ganttChartRendered methods rely on the getTaskRect routine to get a rectangle that specifies the bounds of the task element on the chart.

The getTaskRect method is simple and interesting, because it is quite general:

        function getTaskRect(chart, task) {
            var x1 = chart.axisX.convert(task.start),
                x2 = chart.axisX.convert(task.end),
                index = chart.collectionView.items.indexOf(task),
                y1 = chart.axisY.convert(index - .35),
                y2 = chart.axisY.convert(index + .35);
            return new wijmo.Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
        }

Notice how the method uses the convert method of the appropriate chart axis to convert logical (data) to physical (chart) coordinates.

Our Gantt chart looks a lot more interesting now:

This is a much more useful Gantt chart, and as you can see, it's easy to enhance and customize further.

Feel free to download, run, and play with the code for the GanttChart sample. If you have any feedback on this blog or on the sample, please send it our way.