Advertisement
  1. Web Design
  2. HTML/CSS
  3. JavaScript for Designers

How to Build a Simple Gantt Chart With CSS and JavaScript

Scroll to top

So far in our series of CSS chart tutorials, we’ve learned how to create different types of charts including bar charts, thermometer charts, and pie charts.

Today we’ll continue this journey by building and presenting data in a Gantt chart. Unlike the other chart tutorials, we’ll be making heavy use of JavaScript to implement various aspects of the chart. You’ll be able to use this basis as a Gantt chart template for future projects.

Note: keep in mind that it’s perfectly possible to build a pure CSS Gantt chart thanks to CSS Grid Layout. See how it’s done in this tutorial!

The Gantt Chart We’re Building

Here’s the chart we’ll be creating (hit rerun to see it animate). It shows a series of tasks, making clear when those tasks are scheduled to begin along the course of a week–either at the start of a day, or halfway through–and when they’re due to be completed:

What is a Gantt Chart?

Developed by mechanical engineer Henry Gantt in the run up to WW1, Gantt charts were originally used to manage the logistics of mobilizing American soldiers and munitions. Nowadays Gantt’s techniques are used for way more than warfare; in fact you’ll find them present in most industries.They make it easy to visualize a list of tasks, the dependance of those tasks on one another, and their state of completion.

“A Gantt chart is a type of bar chart that illustrates a project schedule. This chart lists the tasks to be performed on the vertical axis, and time intervals on the horizontal axis. The width of the horizontal bars in the graph shows the duration of each activity.” – Wikipedia
Visualization of a Gantt chartVisualization of a Gantt chartVisualization of a Gantt chart
Visualization of a Gantt chart

Gantt charts give managers the ability to manage projects, schedule tasks, watch progress, delegate responsibilities, and allocate resources. 

In actual fact, you don’t have to be a manager to use them. Anyone who wants to organize their tasks can benefit from this form of project visualization.

Regardless of using it as a management tool, Gantt’s layout can also have other uses. For example, you can use this approach within a corporate site as a timeline for visualizing the company’s history. 

Gantt Solutions

Most project management platforms use Gantt charts as a core part of their offering. ClickUp, Zapier, and Monday.com are all names you’ll hear when project management apps are discussed (though the founders at Monday.com stress their timeline “focuses on people, not tasks or projects”).

Gantt Chart Templates on Envato Elements

Keynote, PowerPoint, and other presentation apps are more than capable of creating Gantt charts for demonstration purposes. Take a look at some of the Gantt chart templates available right now on Envato Elements.

Gantt Chart Keynote TemplateGantt Chart Keynote TemplateGantt Chart Keynote Template
Gantt Chart Keynote Template
Project Status for KeynoteProject Status for KeynoteProject Status for Keynote
Project Status for Keynote

There are also a number of free and proprietary solutions available out there for building your own Gantt charts. You can create one with Microsoft Excel, Google Sheets, a web app like TeamGantt, a JavaScript library like Highcharts, or even by writing your own code. 

In this tutorial we’re going for the latter; let’s create our own simple Gantt chart using CSS and JavaScript!

1. Specify the Page Markup

We’ll begin by defining a wrapper element which contains two lists:

  • The first list defines the chart range (x-axis data). In our case, this will contain the days of the week. Each day will represent normal working hours.
  • The second list sets the chart data (y-axis data). In our case, the data will include the tasks that need to be performed within a week. Each list item, which describes a task, comes with two custom attributes: the data-duration attribute and the data-color attribute. The first attribute defines the task duration, while the second one its background color. The value of the data-duration attribute should be in the [startDay]-[endDay] format. That being said, we’ll use the data-duration="tue-wed" for a task that should start on Tuesday and finish on Wednesday. Furthermore, a task can also start and finish in the middle of a day. In such a case, the data-duration attribute value should be in the [startDay]½-[endDay]½ format. So for example, we’ll use the data-duration="tue-wed½" for a task that should start on Tuesday and finish in the middle of Wednesday. Similarly, the data-duration="tue½-tue" will describe a task that should start in the middle of Tuesday and finish on the same day.

Here’s the required markup:

1
<div class="chart-wrapper">
2
  <ul class="chart-values">
3
    <li>sun</li>
4
    <li>mon</li>
5
    <li>tue</li>
6
    <li>wed</li>
7
    <li>thu</li>
8
    <li>fri</li>
9
    <li>sat</li>
10
  </ul>
11
  <ul class="chart-bars">
12
    <li data-duration="tue-wed" data-color="#b03532">Task</li>
13
    <li data-duration="wed-sat" data-color="#33a8a5">Task</li>
14
    ...
15
  </ul>
16
</div>

2. Style the Chart

For the sake of simplicity, I won’t walk through the initial reset styles, but feel free to look at them by clicking at the CSS tab of the demo project.

The chart wrapper will have a maximum width with horizontally centered content:

1
.chart-wrapper {
2
  max-width: 1150px;
3
  padding: 0 10px;
4
  margin: 0 auto;
5
}

The x-axis

The .chart-values list will be a flex container. Its flex items (days) will be equally distributed across the main axis and have a minimum width of 80px. As a result of that minimum width, on small screens the chart won’t shrink beyond that, and a horizontal scrollbar will appear. Feel free to remove it, if you don’t like this behavior.

To better visualize the left and right boundaries of each item, we’ll use their ::before pseudo-element. We’ll give it a big hardcoded height (510px) which ensures that it will expand to all tasks. Instead of hardcoding that value, there’s always the option to dynamically calculate it through JavaScript. But let’s skip that solution for now, as it’s of secondary importance.

The chart valuesThe chart valuesThe chart values

The corresponding styles:

1
:root {
2
  --divider: lightgrey;
3
}
4
5
.chart-wrapper .chart-values {
6
  position: relative;
7
  display: flex;
8
  margin-bottom: 20px;
9
  font-weight: bold;
10
  font-size: 1.2rem;
11
}
12
13
.chart-wrapper .chart-values li {
14
  flex: 1;
15
  min-width: 80px;
16
  text-align: center;
17
}
18
19
.chart-wrapper .chart-values li:not(:last-child) {
20
  position: relative;
21
}
22
23
.chart-wrapper .chart-values li:not(:last-child)::before {
24
  content: '';
25
  position: absolute;
26
  right: 0;
27
  height: 510px;
28
  border-right: 1px solid var(--divider);
29
}

The y-axis

The items (bars) of the second list will initially be hidden. Specifically, they will have width: 0 and opacity: 0. Plus, we’ll give them position: relative. Later we’ll dynamically set their left position according to the value of their data-duration attribute. 

Here are the related styles:

1
:root {
2
  --white: #fff;
3
}
4
5
.chart-wrapper .chart-bars li {
6
  position: relative;
7
  color: var(--white);
8
  margin-bottom: 15px;
9
  font-size: 16px;
10
  border-radius: 20px;
11
  padding: 10px 20px;
12
  width: 0;
13
  opacity: 0;
14
  transition: all 0.65s linear 0.2s;
15
}
16
17
@media screen and (max-width: 600px) {
18
  .chart-wrapper .chart-bars li {
19
    padding: 10px;
20
  }
21
}

3. Add the JavaScript

When the page loads, or the browser window gets resized, the createChart function will be executed:

1
window.addEventListener("load", createChart);
2
window.addEventListener("resize", createChart);

Note: As I’ve mentioned in other tutorials, there are different ways for limiting the resize events that are emitted. For example, one effective solution is to use Lodash’s _.debounce function. That’s beyond the scope of this tutorial though.

Inside this function, we first do the following things:

  1. Grab the items of the two lists.
  2. Convert the days NodeList into a real array by using the spread operator. Alternatively, we could have used the Array.from() method. This conversion will allow us to take advantage of the filter() method, which is available in arrays, for filtering the days.
  3. Loop through the tasks.
1
function createChart(e) {
2
  // 1

3
  const days = document.querySelectorAll(".chart-values li");
4
  const tasks = document.querySelectorAll(".chart-bars li");
5
  // 2

6
  const daysArray = [...days];
7
  // 3

8
  tasks.forEach(el => {
9
    ...
10
  });
11
}

Loop the Tasks

Next, for each task:

  1. We grab the value of its data-duration attribute (e.g. tue-wed). In addition, we split this value by using the "-" as a separator.
  2. The first string of the returned array represents the starting day of the task (e.g. tue), while the second one its ending day (e.g. wed). 
  3. Knowing its starting day, we filter the daysArray to retrieve the list item (day) that matches this day. During this test, we ignore possible presence of the "½" character. Then, we do a few calculations to calculate the required left property value of the related task.
  4. Knowing its ending day, we filter the daysArray to retrieve the list item (day) that matches this day. During this test, we ignore possible presence of the "½" character. Then, we do a few calculations to calculate the required width property value of the related task.
1
tasks.forEach(el => {
2
    // 1

3
    const duration = el.dataset.duration.split("-");
4
    // 2

5
    const startDay = duration[0];
6
    const endDay = duration[1];
7
    let left = 0,
8
      width = 0;
9
10
    // 3

11
    if (startDay.endsWith("½")) {
12
      const filteredArray = daysArray.filter(day => day.textContent == startDay.slice(0, -1));
13
      left = filteredArray[0].offsetLeft + filteredArray[0].offsetWidth / 2;
14
    } else {
15
      const filteredArray = daysArray.filter(day => day.textContent == startDay);
16
      left = filteredArray[0].offsetLeft;
17
    }
18
    
19
    // 4

20
    if (endDay.endsWith("½")) {
21
      const filteredArray = daysArray.filter(day => day.textContent == endDay.slice(0, -1));
22
      width = filteredArray[0].offsetLeft + filteredArray[0].offsetWidth / 2 - left;
23
    } else {
24
      const filteredArray = daysArray.filter(day => day.textContent == endDay);
25
      width = filteredArray[0].offsetLeft + filteredArray[0].offsetWidth - left;
26
    }
27
    ...
28
  });

Note: In the code above, we use the Element.offsetLeft property to retrieve the left position of an element relative to its parent. Plus, we take advantage of the Element.offsetWidth property to find the element’s width. As another option to grab its width, we could have equally used the more precise Element.getBoundingClientRect() method.

Set Styles

Having calculated the left and width values of each task, the last step is to perform the following actions:

  1. Apply the corresponding styles.
  2. Grab the value of the data-color attribute and set it as the background color of the task.
  3. Show the task. Remember all tasks are initially hidden.
  4. The actions 2 and 3 should run only on page load as their values won’t change each time we resize the browser window.
1
tasks.forEach(el => {
2
  ...  
3
  // 1

4
  el.style.left = `${left}px`;
5
  el.style.width = `${width}px`;
6
  // 4

7
  if (e.type == "load") {
8
    // 2

9
    el.style.backgroundColor = el.dataset.color;
10
    // 3

11
    el.style.opacity = 1;
12
  }
13
 });

Conclusion

That’s it! We managed to build a fully functional Gantt chart. Thanks for following along folks, I hope this provided a good exercise for refreshing your JavaScript skills. Here’s one more look at the final demo:

Play with it and if you find any way to enhance its functionality, be sure to share! Stay tuned for the equivalent CSS Gantt chart.

As always, thanks a lot for reading!

Next Steps

If you want to challenge yourselves, try to convert this vertical timeline into a Gantt chart.

Read More

Spreadsheets, data visualization, Gantt chart templates, graphs, animation–there’s plenty more to dig into!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.