Migrating from an Excel Gantt chart to a Bryntum Gantt chart
Tags: javascript, productivity, tutorial, webdev
Published: 2023-09-21 18:30:45

Microsoft Excel is a common tool used in project management. It's affordable and simple to use, and if you need to create a Gantt chart to schedule project tasks, Excel has many [template Gantt charts](https://create.microsoft.com/en-us/search?query=gantt) to get you started. 

However, you may need more advanced functionality from your Gantt chart than Microsoft Excel provides, for example:

- Scheduling tasks using dependencies and constraints.
- Using recurrent and fixed-time intervals.
- Handling large datasets.
- Customizing the rendering and styling.

If your project would benefit from these capabilities, then the [Bryntum Gantt](https://bryntum.com/products/gantt/) web component is a better choice. 

In this tutorial, we'll show you how to get Gantt chart data from an Excel file into a Bryntum Gantt chart. We'll do the following:

- Write a migration script to convert data from an Excel Gantt chart to a JSON file that can be read by a Bryntum Gantt chart. 
- Set up an Express.js API endpoint to return the JSON data.
- Create a Bryntum Gantt chart that gets data from the API endpoint.

First we'll get data from a simple Excel Gantt chart. Then we'll modify the code to get data from a more complex Gantt chart. You can adjust the code to get data from different Excel Gantt charts. 


## Writing a migration script to convert data from an Excel Gantt chart to a JSON file

Start by cloning the [Excel to Bryntum Server starter GitHub repository](https://github.com/bryntum/excel-to-bryntum-gantt-server-starter). This repository includes a `server.js` file, which contains an Express.js server API endpoint at the "/download" route for GET requests. It also has CORS set up for when we make requests to the API endpoint from our client-side Bryntum Gantt chart. 

You can find the completed code for this tutorial in the `completed-server` branch of the starter repository.

There are two Excel files in the starter's root directory:

- `simple-gantt-chart.xlsx`
- `agile-gantt-chart.xlsx`

These are Excel Gantt chart templates taken from [Microsoft 365 Excel templates](https://create.microsoft.com/en-us/search?query=gantt&filters=excel).

Once you've cloned the repository, install the dependencies by running the following command:

```bash
npm install
```

In the root folder, create a file called `migration-simple-gantt.js`. This is where we'll add the script to read the Excel data from `simple-gantt-chart.xlsx`, convert it to a JSON structure that can be read by a Bryntum Gantt chart, and save the JSON data to a file. 


### Converting Excel file data to a JavaScript object

To read the Excel file and convert the data to JSON format, we'll use the [`xlsx` library](https://www.npmjs.com/package/xlsx). Import the library in your `migration-simple-gantt.js` file:

```js
import xlsx from "xlsx";
```

Add the following lines of code:

```js
// read the Excel file
const workbook = xlsx.readFile("./simple-gantt-chart.xlsx");
const sheetName = workbook.SheetNames[0]; // select the sheet you want
const worksheet = workbook.Sheets[sheetName];

const jsonData = xlsx.utils.sheet_to_json(worksheet);
```

The [`readFile`](https://docs.sheetjs.com/docs/api/#writing-functions) method reads the data from the Excel file and parses it into a workbook object. The Excel file has two sheets, we only want the data from the first sheet. The `workbook.SheetNames` array is an ordered list of the sheets in the Excel workbook. We get the name of the first sheet from the ordered lists of sheets. We then get the worksheet data from the `Sheets` object.    

We use the `sheet_to_json` [utility](https://docs.sheetjs.com/docs/api/#utilities) method to convert the worksheet object into an array of JavaScript objects.


If you `console.log` the `jsonData` variable, you'll see a large array of objects returned:

```js
[
  {
    'Create a Project Schedule in this worksheet.\r\nEnter title of this project in cell B1. \r\nInformation about how to use this worksheet, 
including instructions for screen readers and the author of this workbook is in the About worksheet.\r\nContinue navigating down column A to h
ear further instructions.': 'Enter Company Name in cell B2.',
    'PROJECT TITLE': 'Company Name',
			
    ...
```

Each object in the array is a row of Excel data, for most of the data. You'll notice that a lot of the text returned is not visible in the Excel Gantt chart. This is the text from cells with hidden values for explaining how to use the template worksheet. For example, if you click on cell A1 in the Excel file, you'll see the hidden text in the formula bar. 

The tasks data that we need to extract from the array objects is in the "PROJECT TITLE", \_\_EMPTY", "\_\_EMPTY_1", "\_\_EMPTY_2", "\_\_EMPTY_3", and "\_\_EMPTY_5" property values. 

Let's create a function to extract the tasks data and convert it to the format required by Bryntum Gantt.

### Converting the Excel data to a JSON file compatible with Bryntum Gantt

Later in this tutorial, we'll use the Bryntum Gantt chart [Crud Manager](https://bryntum.com/products/gantt/docs/guide/Scheduler/data/crud_manager) to simplify fetching data from our API. The Crud Manager expects the data it receives from an API endpoint to have a [specific structure](https://bryntum.com/products/gantt/docs/guide/Gantt/data/crud_manager#load-response-structure). We need to extract the data we need from the `jsonData` variable and format it appropriately.

The start date and end date values of the "\_\_EMPTY_2" and "\_\_EMPTY_3" properties are Excel date numbers. Excel stores dates as a [number format](https://docs.sheetjs.com/docs/csf/features/dates/), with the number representing the offset from an epoch. We need a function to convert Excel numbers to JavaScript Date objects. Add the following function to the top of the `migration-simple-gantt.js` file to convert an Excel date number to a JavaScript Date object: 

```js
function excelToJSDate(excelDate) {
  const dateObj = xlsx.SSF.parse_date_code(excelDate);
  return new Date(
    dateObj.y,
    dateObj.m - 1,
    dateObj.d,
    dateObj.H,
    dateObj.M,
    dateObj.S
  );
}
```

The `xlsx` `SSF` (SpreadSheet Format) object has a `parse_date_code` method that parses the Excel number to an [`SFF` date object](https://www.npmjs.com/package/ssf#other-utilities). We use this Date object to create a JavaScript Date object. To construct the JavaScript Date object, we subtract one from the `SFF` date object, as months are zero-indexed in JavaScript.

Now we need a function to create an array of tasks data that can be read by a Bryntum Gantt chart. Add the following function below the `excelToJSDate` function:

```js
function createBryntumTasksRows(data) {
  let taskId = 0;
  let taskStore = [];
  let currentParentId;

  // name of first task is "Phase 1 Title"
  // only start adding tasks after it's found
  const firstTaskName = "Phase 1 Title";
  const exampleTaskName = "Insert new rows ABOVE this one";
  let isFirstTaskFound = false;
  for (let i = 0; i < data.length; i++) {
    if (
      data[i].hasOwnProperty("PROJECT TITLE") &&
      data[i]["PROJECT TITLE"].startsWith("Phase")
    ) {
      if (data[i]["PROJECT TITLE"] === firstTaskName) {
        isFirstTaskFound = true;
      }
      currentParentId = taskId;
      // parent tasks
      taskStore.push({
        id: taskId++,
        name: data[i]["PROJECT TITLE"],
        expanded: true,
      });
    } else if (data[i]["PROJECT TITLE"]) {
      if (!isFirstTaskFound) {
        continue;
      }
      // last task has been added
      if (data[i]["PROJECT TITLE"] === exampleTaskName) {
        break;
      }
      // child tasks
      taskStore.push({
        id: taskId++,
        name: data[i]["PROJECT TITLE"],
        parentId: currentParentId,
        resourceAssignment: data[i]["__EMPTY"],
        percentDone: data[i]["__EMPTY_1"] * 100,
        startDate: excelToJSDate(data[i]["__EMPTY_2"]),
        endDate: excelToJSDate(data[i]["__EMPTY_3"]),
        manuallyScheduled: true,
      });
    }
  }

  return taskStore;
}
```

This function loops through the objects of the Excel `data` that we pass in as arguments. These objects mostly represent Excel rows. We construct a `taskStore` array to store the new task objects. We only start adding to the `taskStore` once the `firstTaskName` is found. 

For this specific Gantt data, we determine which tasks are parent tasks by checking if the "PROJECT TITLE" starts with the text "Phase". We create a unique `id` for each task using the `taskId` variable, which increments on each loop. 

If the "PROJECT TITLE" does not start with the text "Phase", the task is made a child of the last parent task using the `parentId` property. Once the `exampleTaskName` is reached, we break out of the loop.  

Now add the following variable below the `jsonData` variable to create the task rows:

```js
const tasksRows = createBryntumTasksRows(jsonData);
```

If you `console.log` the `tasksRows`, you'll see the following array:

```js
[
  { id: 0, name: 'Phase 1 Title', expanded: true },
  {
    id: 1,
    name: 'Task 1',
    parentId: 0,
    resourceAssignment: 'Name',
    percentDone: 50,
    startDate: 'Mon May 15 2023 00:00:00 GMT+0200 (South Africa Standard Time)',
    endDate: 'Thu May 18 2023 00:00:00 GMT+0200 (South Africa Standard Time)',
    manuallyScheduled: true
  },
  {
    id: 2,
    ...
``` 

If the `startDate` and `endDate` values are behind what they should be, it may be due to the Excel `TODAY()` function that's used to determine the dates in the Excel file. The `TODAY()` function is a volatile function, which means that it recalculates every time the spreadsheet is opened. When you read the Excel file using a library like `xlsx`, it won't trigger these formulas to recalculate. It will read the last calculated value from when the file was last opened and saved in Excel. If the dates are behind what they should be, make a small change to the Excel file, save the change, and try again.

Now, we need to add a `resources` property and an `assignments` property for the `JSON` object that we'll create. The `resources` property will populate the [ResourceStore](https://bryntum.com/products/gantt/docs/api/Gantt/model/ProjectModel#property-resourceStore) of the Bryntum Gantt, which holds the resources that can be assigned to tasks. The simple Gantt chart only has a single resource, "Name", that's assigned to the first child task. The `assignments` property will show which tasks resources are assigned to.

Add the following lines of code below the `tasksRows` variable:

```js
// create resources
const resourceNames = new Set();
const resourcesRows = tasksRows
  .filter(
    (item) =>
      item.resourceAssignment && !resourceNames.has(item.resourceAssignment)
  )
  .map((item, i) => {
    const name = item.resourceAssignment;
    return {
      id: i,
      name,
    };
  });
```

We determine the resources of the data using the `resourceAssignment` property of the `tasksRows` data. We then create a `resourcesRows` array, where each object in the array is a resource. 

If you `console.log` the `resourcesRows` variable, you'll see the following array:

```js
[ { id: 0, name: 'Name' } ]
```

Now let's create the rows for assignments. Add the following lines of code below the `resourcesRows` variable:

```js
// create assignments
const resourcesWithAssignments = tasksRows.filter(
  (item) => item.resourceAssignment
);

const assignmentsRows = resourcesWithAssignments.map((item, i) => {
  const resource = resourcesRows.find(
    (resource) => resource.name === item.resourceAssignment
  );
  return {
    id: i,
    event: item.id,
    resource: resource.id,
  };
});
```

To determine the tasks (events) that are assigned to a resource, we first find tasks that have a resource assignment. We then use the tasks to get the resource `id` and construct the `assignmentsRows` array.


If you `console.log` the `assignmentsRows` variable, you'll see the following array:

```js
[ { id: 0, event: 1, resource: 0 } ]
```

Now create an object for the data that will be sent to the Bryntum Gantt chart:

```js
// convert JSON data to the expected load response structure
const ganttLoadResponse = {
  success: true,
  tasks: {
    rows: tasksRows,
  },
  resources: {
    rows: resourcesRows,
  },
  assignments: {
    rows: assignmentsRows,
  },
};
```

Next we need to convert this object to JSON format and save it in a JSON file.

### Saving the data to a JSON file

Add the following lines of code below the `ganttLoadResponse` variable:

```js
const dataJson = JSON.stringify(ganttLoadResponse, null, 2); // convert the data to JSON, indented with 2 spaces

// define the path to the data folder
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const dataPath = path.join(__dirname, "data");

// ensure the data directory exists
if (!fs.existsSync(dataPath)) {
  fs.mkdirSync(dataPath);
}

// define the path to the JSON file in the data folder
const filePath = path.join(dataPath, "simple-gantt-chart.json");

// write the JSON string to a file in the data directory
fs.writeFile(filePath, dataJson, (err) => {
  if (err) throw err;
  console.log("JSON data written to file");
});
```

Import the Node modules required:

```js
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
```

We convert the JavaScript object to a JSON string. We then create a JSON file called `simple-gantt-chart.json` in the `data` folder using the Node File System module and add the Excel JSON data to it.

To run this script, use the following command:

```bash
node migration-simple-gantt.js
```


This will create a `simple-gantt-chart.json` file in the `data` folder.

For this tutorial, we simply save Excel Gantt data to a JSON file on the server. If you want to persist the data to a database, you can check out the [saving data](https://www.bryntum.com/products/gantt/docs/guide/Scheduler/data/crud_manager#saving-data) section of our docs

Next, we'll set up an API endpoint that will send the JSON data to the Bryntum Gantt chart. 

## Setting up an Express server with an API endpoint

In the `server.js` file, there is an API endpoint at the "/download" route for GET requests. Replace the code in the `try` block with the following lines of code:

```js
const data = await fs.readFile("./data/simple-gantt-chart.json", "utf-8");
const jsonData = JSON.parse(data);
res.json(jsonData);
```

Import the Node File System module with promise-based functions:

```js
import fs from "fs/promises";
```

We read the JSON data from the file system, parse it, and then return it from the API as a JSON response.

Now start the server using the following command:

```bash
npm run start
```

If you make a GET request to your API endpoint at `http://localhost:3000/download`, you should see the following response:

```json
{
	"success": true,
	"tasks": {
		"rows": [
			{
				"id": 0,
				"name": "Phase 1 Title",
				"expanded": true
			},
			{
				"id": 1,
				"name": "Task 1",
				"parentId": 0,
				"resourceAssignment": "Name",
				"percentDone": 50,
				"startDate": "Mon May 15 2023 00:00:00 GMT+0200 (South Africa Standard Time)",
				"endDate": "Thu May 18 2023 00:00:00 GMT+0200 (South Africa Standard Time)",
				"manuallyScheduled": true
			},
      ...
```

Now we can create a Bryntum Gantt chart that will get data from this API endpoint.

## Creating a client-side Bryntum Gantt chart

To get started, clone the following [Excel to Bryntum Client starter GitHub repository](https://github.com/bryntum/excel-to-bryntum-gantt-client-starter). It has a "completed-gantt" branch that shows the code for the completed tutorial. It uses [Vite](https://vitejs.dev/), which is a development server and JavaScript bundler. Install the Vite dev dependency by running the following command:

```bash
npm install
```

Next, install the Bryntum Gantt component by following our [guide to installing the Gantt chart component using npm](https://www.bryntum.com/docs/gantt/guide/Gantt/quick-start/javascript-npm).

The `main.js` file contains code for a basic Bryntum Gantt chart. The [`project`](https://www.bryntum.com/products/gantt/docs/guide/Gantt/data/crud_manager#gantt-stores) config property is used to configure the data stores the Gantt chart uses. The `tasksData` property contains some example inline data. To simplify the data structure, the [`taskStore`](https://www.bryntum.com/products/gantt/docs/api/Gantt/data/TaskStore) is configured to consume a flat dataset, where the task parents are defined using the `parentId` property. Flat data is converted to tree data with `autotree` and `transformFlatData` so that the Bryntum Gantt can parse it.

Start the development server by running the following command:

```bash
npm run dev
```

You should see a Gantt chart with one parent task and one child task.

Next, we'll use the Bryntum Gantt Crud Manager to get the Excel data from our API.

## Getting the data from the API endpoint

First, remove the `tasksData` property from the `gantt` config in the `main.js` file.

Add the following properties to the `gantt` config, below the `taskStore` property:

```js
  transport: {
    load: {
      url: "http://localhost:3000/download",
    },
  },
  autoLoad: true,
  validateResponse: true,
```

The `transport` property is used to populate the Gantt chart data stores. We configured the `load` property to get data from our server API endpoint. We set the [`autoLoad`](https://www.bryntum.com/products/gantt/docs/api/Core/data/AjaxStore#config-autoLoad) and [`validateResponse`](https://www.bryntum.com/products/gantt/docs/api/Scheduler/crud/mixin/AbstractCrudManagerValidation#config-validateResponse) properties to `true` to initiate a data load when the store is instantiated and to check the response structure of requests made by the Crud Manager, respectively.

You can also configure `transport` to sync data changes to a specific URL. To learn more, you can read the [saving data](https://www.bryntum.com/products/gantt/docs/guide/Scheduler/data/crud_manager#saving-data) section of the Crud Manager guide in our docs.

We need to add columns to our Gantt chart to match the headers of the Excel Gantt chart. Add the following objects to the `columns` property of the `gantt` config:

```js
    {
      type: "resourceassignment",
      text: "Assigned Resources",
      showAvatars: false,
      width: 160,
    },
    {
      type: "percent",
      text: "PROGRESS",
      field: "renderedPercentDone",
      showValue: true,
      width: 160,
    },
    { type: "date", field: "startDate", text: "START", width: 110 },
    { type: "date", field: "endDate", text: "END", width: 110 },
```

Now run the client and server development servers and you'll see the Bryntum Gantt chart with the data from the Excel file:

![Bryntum Gantt chart with simple Excel Gantt data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szgr5xymm4zirx3w8hfj.png)

The Bryntum Gantt chart has a lot of functionality out of the box, such as:

- Configurable and sortable task columns. Right-click on a column header to see the options.
- Collapsable task groups.
- Drag-and-drop reordering of tasks.
- Add, edit, copy, and delete tasks. Right-click on a task to see a pop-up menu with these actions and more, including editing dependencies.
- Draggable task durations.
- Resizeable task durations. Hover over the left or right side of a task duration until the cursor changes to a resize icon. Then click and drag left or right.
- Create task dependencies. Hover over the left or right side of a task duration until you see a circle icon. Then click and drag to connect the task to another task.
- Change date ranges or filter tasks by name by right-clicking on one of the date headers.

Now, let's get Excel data from a more complex Excel Gantt chart.

## Getting data from a more complex Excel Gantt chart

The data source we'll use for this part of the tutorial is the `agile-gantt-chart.xlsx` Excel file, which will require changes to our server and client code. 

### Changes to the server: Writing a new migration script

Make a copy of the `migration-simple-gantt.js` file and rename it `migration-agile-gantt.js`. 

The data we need from the `agile-gantt-chart.xlsx` Excel file is in sheet two. Change the `workbook` and `sheetName` variables in the `migration-agile-gantt.js` file to get data from the second workbook sheet of the agile Gantt chart: 

```js
    const workbook = xlsx.readFile("./agile-gantt-chart.xlsx");
    const sheetName = workbook.SheetNames[1];
```

Remove the `jsonData` variable:

```js
    const jsonData = xlsx.utils.sheet_to_json(worksheet);
```

Replace it with the following lines of code:

```js
    const csv = xlsx.utils.sheet_to_csv(worksheet);
    const jsonData = await csvToJson().fromString(csv);
```

Instead of converting the Excel data directly to JSON, we first convert it to CSV values. We then use the [`csvtojson`](https://github.com/Keyang/node-csvtojson) library to convert the CSV values to a JSON string. 

Make sure that you import the `csvToJson` function from the `csvtojson` library:

```js
import csvToJson from "csvtojson";
```

We use this method to convert the Excel data to a JSON data object as the JSON object output is easier to parse than the output we used for the `simple-gantt-chart.xlsx` Excel file. You might like to try each method to see which works best for you. 

If you `console.log` the `jsonData` object, you'll see a large array of objects:

```js
[
	{
		field1: "",
		field2: "PROJECT: Light",
		field3: "",
		...
		field68: "",
	},
	{
		field1: "",
		field2: "",
		...
		field68: "",
	},
	  ...
```

Each object in the "data" array represents a row in the Excel file. The columns are named incrementally from "field1" to "field68".  

The data we need for the Gantt chart is in columns two to seven of the Excel chart, which correspond to the properties `field2` to `field7` of the "data" array objects. Unlike the previous Gantt chart we imported data from, there is no end date for each task in this chart. Instead, each task has a start date and a duration in days. This Gantt chart also has a "Categories" column.

The start date values of the `field6` keys are not in Excel date number format, so we don't need the `excelToJSDate` function. Replace it with the following function:

```js
function convertToDate(dateStr) {
  let [month, day, year] = dateStr.split("/");
  year = year.length === 2 ? "20" + year : year;
  month = month - 1;
  return new Date(year, month, day);
}
```

This function converts the date string to a JavaScript Date object.

Now, replace the existing `createBryntumTasksRows` with the one below:

```js
function createBryntumTasksRows(data) {
  let taskId = 0;
  let taskStore = [];
  let currentParentId = null;

  // name of first task is "Phase 1 Title"
  // only start adding tasks after it's found
  const firstTaskName = "Project development";
  const exampleTaskName = "Task 4";
  let isFirstTaskFound = false;
  for (let i = 0; i < data.length; i++) {
    // check for first task
    if (data[i]["field2"] === firstTaskName) {
      isFirstTaskFound = true;
    }
    // check for example task
    if (data[i]["field2"] === exampleTaskName) {
      break;
    }

    if (isFirstTaskFound) {
      // parent task
      if (data[i]["field6"] === "") {
        currentParentId = taskId;
        taskStore.push({
          id: taskId++,
          name: data[i]["field2"],
          expanded: true,
        });
      } else {
        // child tasks
        taskStore.push({
          id: taskId++,
          name: data[i]["field2"],
          parentId: currentParentId,
          category: data[i]["field3"],
          resourceAssignment: data[i]["field4"] ? data[i]["field4"] : undefined,
          percentDone: data[i]["field5"].slice(0, -1) * 1,
          startDate: convertToDate(data[i]["field6"]),
          duration: data[i]["field3"] === "Milestone" ? 0 : data[i]["field7"],
          manuallyScheduled: true,
        });
      }
    }
  }

  return taskStore;
}
```

This function is similar to the one we used for the simple Gantt chart. We check if a task is a parent task if the value of the `"field6"` property is an empty string. The child tasks have no `endDate` property and there's an additional `category` property. The `duration` property is set to `0` if the category is "Milestone". This will indicate to the Bryntum Gantt that the task is a milestone and the task bar will be styled like a milestone, diamond-shaped. The `resources` and `assignments` properties will be created in the same way as previously. 

Change the name of the `filePath` that the JSON file will be written to:

```js
const filePath = path.join(dataPath, "agile-gantt-chart.json");
```


In the `server.js` file, change the JSON file that the `fs.readFile` method reads from:

```js
    const data = await fs.readFile("./data/agile-gantt-chart.json", "utf-8");
```

Run the migration script to create the `agile-gantt-chart.json` file:

```bash
node migration-agile-gantt.js
```

We'll now change our client-side Bryntum Gantt chart to resemble the structure of the Excel Gantt chart.

### Changes to the client

First, in the `main.js` file, we'll add extra `gantt` columns and adjust the width of the columns. Change the `columns` configuration to the following:

```js
  columns: [
    { type: "name", width: 190, text: "Milestone description" },
    { width: 80, text: "Category", field: "category" },
    {
      type: "resourceassignment",
      text: "Assigned to",
      showAvatars: false,
      width: 100,
    },

    {
      type: "percent",
      text: "PROGRESS",
      field: "renderedPercentDone",
      showValue: true,
      width: 100,
    },
    { type: "date", field: "startDate", text: "START", width: 110 },
    { type: "date", field: "endDate", text: "END", width: 110 },
  ],
```

You'll now see the following Bryntum Gantt chart when you run the development server:

![Bryntum Gantt chart with Excel agile Gantt data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihofbzd2utmxw6uefqgr.png)

Notice that the "Category" values are not displayed. This is because "Category" is not one of the predefined fields of the Bryntum Gantt task store [Model](https://bryntum.com/products/gantt/docs/api/Core/data/Model). We can add this custom field to the model by adding the following lines of code to the bottom of the `main.js` file:

```js
gantt.taskStore.modelClass.addField({
  name: "category",
  type: "string",
});
```

The "Category" values will now be displayed in your Gantt chart:

![Bryntum Gantt chart with Excel agile Gantt data and categories](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kv3wqmlfp69av1rixr8l.png)

If you look at the `agile-gantt-chart.xlsx` Gantt chart, you'll see that the task bars are colored depending on the task category, and the milestones are shown as a flag. Let's color the child task bars based on the task category. Add the following method in the `gantt` config, below the `columns` property: 

```js
  // custom taskbar content
  taskRenderer({ taskRecord, renderData }) {
    const { category, parentId } = taskRecord;
    if (parentId !== null) {
      switch (category) {
        case "Goal":
          renderData.style = "background-color:" + "#d65532";
          break;
        case "Milestone":
          renderData.style = "background-color:" + "none";
          break;
        case "Low Risk":
          renderData.style = "background-color:" + "#00b0f0";
          break;
        case "Med Risk":
          renderData.style = "background-color:" + "#4868e5";
          break;
        case "High Risk":
          renderData.style = "background-color:" + "#852c98";
          break;
        case "On Track":
          renderData.style = "background-color:" + "#4ca472";
          break;
        default:
          renderData.style = "background-color:" + "#e6e6e6";
      }
    }

    return "";
  },
```

The [`taskRenderer`](https://bryntum.com/products/gantt/docs/api/Gantt/view/GanttBase#config-taskRenderer) is called each time a task is rendered. You can use it to change what's displayed in the task bars. It returns a string that's displayed in the task bar. The string can be text or HTML. You can also return a DOM config object. 

We can get information about the task being rendered from the `taskRecord` parameter. If the task has a `parentId`, it's a child task. For the child tasks, we use a `switch` statement to determine the category of the task. We then style the task bars based on their category using the `renderData` parameter. We style the task bars by changing their `"background-color"` CSS property. 

To make the "Milestone" category task bars look like a flag, add the following lines of code directly above the return statement:

```js
    if (category === "Milestone")
      return `<span style="position:absolute; top: -10px"><i class="b-fa b-fa-flag b-fa-2xs" style="color: orange;"></i></span>`;
```

If the task is a Milestone, we'll render a [Font Awesome](https://fontawesome.com/) flag icon on top of the task bar. The version of Font Awesome used by Bryntum components has a `b-fa` prefix instead of `fa` to prevent conflicts with other versions of Font Awesome on the page. If you are rendering user-provided data, you can use the [`xss`](https://bryntum.com/products/gantt/docs/api/Core/helper/StringHelper#function-xss-static) function to sanitize the rendered string or HTML.

The Milestone task bars will have a flag icon rendered on top of them:

![Bryntum Gantt - milestone with flag issue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68qcgus3mwjbtw8fw3lz.png)

We can use CSS to hide the underlying task bar diamond shape. Add the following lines to the `style.css` file:

```css
.b-gantt-task-wrap.b-milestone-wrap
  .b-gantt-task:not(.b-gantt-task-withicon)
  .b-gantt-task-content::before {
  height: 0;
  width: 0;
}
```

It's often easiest to use your browser dev tools to determine which HTML elements you want to apply the CSS styling to.

You'll now only see the flag icon for the Milestone task bars:

![Bryntum Gantt - milestone with flag](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9owmb6jgbfi5vvoipzc1.png)

## Next steps 

Using the code in this tutorial as a guide, try to migrate your own Excel Gantt charts to a Bryntum Gantt chart to take your project management to the next level.  

You can also persist the data to a database. Check out the [saving data](https://www.bryntum.com/products/gantt/docs/guide/Scheduler/data/crud_manager#saving-data) section of our docs or take a look at one of these tutorials from our blog:
- [Migrating from DHTMLX Gantt to Bryntum Gantt](https://bryntum.com/blog/migrate-from-dhtmlx-gantt-to-bryntum-gantt/): Learn how to set up a MySQL database for a Bryntum Gantt chart.
- [Creating a Bryntum Gantt chart with React, TypeScript, Prisma, and SQLite: Remix vs. Next.js](https://bryntum.com/blog/creating-a-bryntum-gantt-chart-with-react-typescript-prisma-and-sqlite-remix-vs-next-js/): Learn how to set up a SQLite database for a Bryntum Gantt chart.