Guide · 2026-09-19

How to Make a Gantt Chart in Python: A Step-by-Step Guide

Building a Gantt chart in Python can feel harder than it should. You need task names, dates, durations, dependencies, and a visual layout that people can understand quickly. A small mistake in date handling can shift an entire schedule.

That frustration grows when a basic bar chart fails to show milestones, overlaps, or delayed work clearly. You may spend more time adjusting colors and axes than analyzing the project plan.

But here's the truth: you can create a useful Gantt chart with Python in a few clear steps. This guide uses Plotly to build an interactive chart, explains the logic behind each part, and shows how to improve it for real project planning.

How to Create a Gantt Chart in Python

To make a Gantt chart in Python, prepare task data with start and finish dates, then pass it to a charting library such as Plotly or Matplotlib. Plotly is usually the easiest choice because it provides interactive hover details, zooming, and date-based axes.

You will complete the process in six steps:

  1. Install an appropriate Python charting library.
  2. Define tasks with names, start dates, finish dates, and categories.
  3. Convert the task details into a structured format.
  4. Create horizontal timeline bars.
  5. Customize colors, labels, and milestones.
  6. Export or share the finished visualization.

Step 1: Install Plotly

Open your terminal and install Plotly with the following command:

pip install plotly

Plotly Express provides a concise interface for common visualizations. It works well for project schedules because each task can become a horizontal bar between two dates.

If you work inside a Jupyter Notebook, install the package in the same Python environment used by the notebook. Otherwise, your code may run in one environment while the library exists in another.

Step 2: Define the Project Tasks

Each task needs at least four pieces of information:

Here is a small website launch schedule:

tasks = [
    {
        "Task": "Requirements",
        "Start": "2025-04-01",
        "Finish": "2025-04-04",
        "Phase": "Planning"
    },
    {
        "Task": "Design",
        "Start": "2025-04-03",
        "Finish": "2025-04-10",
        "Phase": "Design"
    },
    {
        "Task": "Development",
        "Start": "2025-04-08",
        "Finish": "2025-04-18",
        "Phase": "Build"
    },
    {
        "Task": "Testing",
        "Start": "2025-04-16",
        "Finish": "2025-04-22",
        "Phase": "Quality"
    },
    {
        "Task": "Launch",
        "Start": "2025-04-23",
        "Finish": "2025-04-23",
        "Phase": "Release"
    }
]

The schedule includes overlapping work. Design begins before requirements finish, while development overlaps with design. This makes the chart more useful than a simple sequential list.

Step 3: Create a DataFrame

Plotly can work with a list of dictionaries, although a pandas DataFrame makes filtering and transformation easier.

import pandas as pd

df = pd.DataFrame(tasks)

df["Start"] = pd.to_datetime(df["Start"])
df["Finish"] = pd.to_datetime(df["Finish"])

Converting the date columns with pd.to_datetime() helps Python interpret them as dates. It also reduces problems when you sort, filter, or calculate schedule durations later.

For a larger schedule, you can add columns such as owner, priority, completion percentage, or dependency. Keep the basic timeline fields consistent across every task.

Step 4: Build the Gantt Chart

Now import Plotly Express and create a timeline:

import plotly.express as px

fig = px.timeline(
    df,
    x_start="Start",
    x_end="Finish",
    y="Task",
    color="Phase",
    title="Website Launch Schedule",
    hover_name="Task"
)

fig.update_yaxes(autorange="reversed")
fig.show()

The px.timeline() function creates a horizontal bar for each task. The left edge represents the start date, while the right edge represents the finish date.

The reversed vertical axis places the first task at the top. Without this adjustment, Plotly may display the first row near the bottom, which feels unnatural for a project schedule.

When you hover over a bar, Plotly displays task details. You can zoom into a week, drag across the timeline, and inspect overlapping activities.

Step 5: Improve the Layout

A default chart works, although a few adjustments make it easier to read:

fig.update_layout(
    xaxis_title="Date",
    yaxis_title="Task",
    height=600,
    showlegend=True,
    title_font_size=22,
    margin=dict(l=140, r=40, t=80, b=60)
)

fig.update_xaxes(
    showgrid=True,
    gridcolor="lightgray"
)

fig.show()

The left margin gives longer task names enough room. A taller chart prevents labels from crowding each other, especially when the schedule contains many activities.

Step 6: Add a Milestone Marker

A milestone usually represents an event with little or no duration. The launch task in the example starts and ends on the same day, so its bar may appear very narrow.

You can add a vertical marker for the launch date:

fig.add_vline(
    x="2025-04-23",
    line_width=2,
    line_dash="dash",
    line_color="red"
)

fig.add_annotation(
    x="2025-04-23",
    y=0.98,
    yref="paper",
    text="Launch",
    showarrow=False,
    font=dict(color="red")
)

fig.show()

This makes the milestone visible even when the timeline contains many overlapping bars. Use markers for events such as approvals, releases, inspections, or customer handoffs.

Complete Example

Here is the complete version in one place:

import pandas as pd
import plotly.express as px

tasks = [
    {
        "Task": "Requirements",
        "Start": "2025-04-01",
        "Finish": "2025-04-04",
        "Phase": "Planning"
    },
    {
        "Task": "Design",
        "Start": "2025-04-03",
        "Finish": "2025-04-10",
        "Phase": "Design"
    },
    {
        "Task": "Development",
        "Start": "2025-04-08",
        "Finish": "2025-04-18",
        "Phase": "Build"
    },
    {
        "Task": "Testing",
        "Start": "2025-04-16",
        "Finish": "2025-04-22",
        "Phase": "Quality"
    },
    {
        "Task": "Launch",
        "Start": "2025-04-23",
        "Finish": "2025-04-23",
        "Phase": "Release"
    }
]

df = pd.DataFrame(tasks)
df["Start"] = pd.to_datetime(df["Start"])
df["Finish"] = pd.to_datetime(df["Finish"])

fig = px.timeline(
    df,
    x_start="Start",
    x_end="Finish",
    y="Task",
    color="Phase",
    title="Website Launch Schedule",
    hover_name="Task"
)

fig.update_yaxes(autorange="reversed")

fig.update_layout(
    xaxis_title="Date",
    yaxis_title="Task",
    height=600,
    showlegend=True,
    title_font_size=22,
    margin=dict(l=140, r=40, t=80, b=60)
)

fig.update_xaxes(
    showgrid=True,
    gridcolor="lightgray"
)

fig.add_vline(
    x="2025-04-23",
    line_width=2,
    line_dash="dash",
    line_color="red"
)

fig.show()

How Gantt Charts Represent Project Schedules

A Gantt chart maps work against time. Each horizontal bar answers three questions quickly: what work is happening, when does it happen, and how long should it take?

For example, a development bar running from April 8 through April 18 shows the planned development window. If testing begins on April 16, the overlap becomes immediately visible.

Here's why this matters: schedules often contain hidden relationships. A task may appear short in a task list, yet it could delay several later activities. A timeline exposes that risk visually.

Tasks, Milestones, and Dependencies

A task has a duration, while a milestone marks a point in time. Dependencies connect activities that rely on one another.

Suppose design must finish before development can begin. The chart should show development starting after the design finish date. If both bars overlap, the schedule may reflect parallel work or an incorrect dependency.

Plotly can show the timing of work, although it does not automatically calculate every project dependency. You need to create and validate those relationships in your planning logic.

Duration Calculations

You can calculate task duration with pandas:

df["Duration"] = (df["Finish"] - df["Start"]).dt.days

This produces the number of elapsed days between the two dates. If you want inclusive calendar days, add one:

df["Inclusive Duration"] = (
    df["Finish"] - df["Start"]
).dt.days + 1

For example, a task beginning on April 1 and ending on April 4 spans four calendar dates. Clarify your counting method before comparing estimates with actual schedules.

Choosing Between Plotly and Matplotlib

Plotly is a strong choice when you want interaction. Matplotlib can be useful when you need a static image, fine-grained styling, or compatibility with an existing reporting workflow.

Requirement Recommended choice
Zooming and hover details Plotly
Static image generation Matplotlib
Quick timeline prototype Plotly Express
Highly customized rendering Matplotlib
Browser-based sharing Plotly

The best choice depends on how people will use the chart. An interactive timeline helps a project team inspect dates, while a static image may suit a presentation slide.

A Matplotlib Alternative

Here is a basic Matplotlib approach:

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime

activities = [
    ("Requirements", "2025-04-01", "2025-04-04"),
    ("Design", "2025-04-03", "2025-04-10"),
    ("Development", "2025-04-08", "2025-04-18"),
    ("Testing", "2025-04-16", "2025-04-22")
]

fig, ax = plt.subplots(figsize=(10, 5))

for index, (name, start, finish) in enumerate(activities):
    start_date = datetime.strptime(start, "%Y-%m-%d")
    finish_date = datetime.strptime(finish, "%Y-%m-%d")
    duration = (finish_date - start_date).days

    ax.barh(
        index,
        duration,
        left=mdates.date2num(start_date),
        height=0.5
    )

ax.set_yticks(range(len(activities)))
ax.set_yticklabels([item[0] for item in activities])
ax.xaxis_date()
ax.set_title("Website Launch Schedule")
ax.set_xlabel("Date")
ax.set_ylabel("Task")

plt.tight_layout()
plt.show()

Matplotlib requires more manual work because you control the bar positions and date formatting yourself. That extra control can help when you need a carefully styled static chart.

Adding Progress, Owners, and Status

A timeline becomes more useful when it shows the current state of each activity. Add columns for owner, status, or completion percentage to your task structure.

tasks = [
    {
        "Task": "Requirements",
        "Start": "2025-04-01",
        "Finish": "2025-04-04",
        "Phase": "Planning",
        "Owner": "Maya",
        "Status": "Complete",
        "Progress": 100
    },
    {
        "Task": "Design",
        "Start": "2025-04-03",
        "Finish": "2025-04-10",
        "Phase": "Design",
        "Owner": "Leo",
        "Status": "In progress",
        "Progress": 60
    }
]

You can display additional fields in the hover area:

fig = px.timeline(
    df,
    x_start="Start",
    x_end="Finish",
    y="Task",
    color="Status",
    hover_name="Task",
    hover_data=["Owner", "Progress", "Phase"]
)

fig.update_yaxes(autorange="reversed")
fig.show()

Coloring by status gives the team a quick visual summary. Green might represent completed work, blue active work, and gray planned work.

Keep the color scheme restrained. If every task has a different color, the chart becomes a legend puzzle instead of a planning aid.

Showing Completion with Overlay Bars

Plotly timeline bars represent the planned duration. To show completion inside each bar, create a second set of shorter bars that represent completed portions.

For a task lasting ten days with 60 percent progress, the completion overlay would cover six days. This approach requires a calculated progress finish date:

df["Progress Finish"] = df["Start"] + (
    df["Finish"] - df["Start"]
) * (df["Progress"] / 100)

You can then create a second timeline with Start and Progress Finish. Overlaying both charts requires careful layout control, so status colors may be easier for a first version.

Handling Dates, Weekends, and Time Zones

Date handling causes many Gantt chart errors. A schedule using text values can sort April 10 before April 2 if Python treats the values as ordinary characters.

Always convert dates before sorting or calculating:

df["Start"] = pd.to_datetime(df["Start"], errors="coerce")
df["Finish"] = pd.to_datetime(df["Finish"], errors="coerce")

The errors="coerce" option changes invalid values into missing dates. You can then find them with:

invalid_dates = df[
    df["Start"].isna() | df["Finish"].isna()
]

print(invalid_dates)

Excluding Weekends

A calendar-day duration includes Saturdays and Sundays. A business schedule may require working-day calculations instead.

df["Business Days"] = df.apply(
    lambda row: len(
        pd.bdate_range(row["Start"], row["Finish"])
    ),
    axis=1
)

This example counts weekdays between the start and finish dates. It does not account for public holidays, custom work patterns, or regional calendars.

For a global team, time zones can also affect milestone timing. Store dates consistently and decide whether a milestone represents a local date or a precise time.

Exporting and Sharing the Chart

Plotly charts can be displayed in a notebook, browser, dashboard, or web application. You can also save an interactive HTML version:

fig.write_html("project_schedule.html")

You can export a static image after installing the required rendering package:

pip install kaleido
fig.write_image("project_schedule.png")

Use an interactive version when people need to inspect several weeks or hover over task details. Use an image when the chart belongs in a presentation or printed report.

Before sharing, check the visible date range, task order, legend labels, and milestone markers. A technically correct chart can still confuse people if the labels are too small or the timeline begins far before the first activity.

Natural Project Planning Solution: ONES.com

ONES.com product screenshot

Value Proposition

Python is useful for creating a customized Gantt visualization. ONES.com gives you a broader project management and knowledge management platform when your team needs planning, execution, reporting, and collaboration in one environment.

ONES Project is a Jira alternative with built-in scheduling capabilities, while ONES Wiki provides a knowledge base for project context. They are sold separately.

Core Capabilities

Application Scenarios

Software release planning: An engineering team can connect sprint work, testing, approvals, and release milestones in one project workspace. A Python chart may still help with specialized reporting, while the active plan remains accessible to the people managing delivery.

Air-gapped project operations: A regulated team can run project management in an air-gapped environment. This is useful when external connectivity is restricted and the team still needs structured workflows, reporting, and milestone visibility.

Project knowledge coordination: A team can use ONES Project for schedules and ONES Wiki for requirements, decisions, and operating guidance. Keeping these areas connected helps explain why a milestone exists and what the team must complete before it.

Common Challenges and Practical Solutions

Bars Appear in the Wrong Order

Problem: The first activity appears at the bottom, making the chart harder to scan.

Solution: Reverse the vertical axis with fig.update_yaxes(autorange="reversed"). This places the first task near the top and follows the usual reading direction.

Dates Are Treated as Text

Problem: Sorting and duration calculations produce strange results because date values remain strings.

Solution: Convert the relevant columns with pd.to_datetime() before sorting, filtering, or subtracting dates.

Milestones Are Barely Visible

Problem: A one-day activity creates an extremely narrow bar.

Solution: Add a vertical line, annotation, or scatter marker for the milestone. Keep the date in the task records so the event remains part of the schedule.

Too Many Colors Create Confusion

Problem: Coloring every task separately makes the legend difficult to interpret.

Solution: Color by a meaningful category such as phase, status, or team. Use labels and hover details for secondary information.

The Chart Shows Plans but No Current Status

Problem: Planned bars do not reveal whether work is complete, delayed, or active.

Solution: Add status or progress fields. You can use colors for a quick summary and overlays when exact completion percentages matter.

FAQs About Python Gantt Charts

Which Python library is easiest for a Gantt chart?

Plotly Express is usually the easiest starting point. Its px.timeline() function accepts start dates, finish dates, task labels, and colors with little setup. It also creates interactive charts with zooming and hover details. Matplotlib offers more manual control and works well for static visualizations, although you need to calculate bar positions and date formatting yourself.

Can I create a Gantt chart without pandas?

Yes. Plotly can receive a list of dictionaries, so pandas is optional for a small schedule. pandas becomes valuable when you need date conversion, filtering, sorting, duration calculations, missing-value checks, or progress calculations. For a handful of activities, a plain list is simple. For a schedule with many fields, a DataFrame usually keeps the code easier to maintain.

How do I show dependencies in Plotly?

Plotly timelines show when tasks occur, although dependency arrows require additional work. You can add annotations with arrows between task bars, create a separate edge layer, or use another visualization approach. First calculate the task coordinates and dependency dates. For a small chart, annotations are practical. For a complex dependency network, a project management platform may be easier to maintain.

How can I show task completion?

Add a progress percentage to each task, then calculate the date reached by that percentage. For example, a task running for ten days at 60 percent completion has a progress endpoint around six days after its start. You can show progress with status colors or create a second timeline containing shorter overlay bars. Status colors are simpler, while overlays communicate more precise progress.

Can a Python Gantt chart exclude weekends?

Yes. Use pandas business-date functions such as pd.bdate_range() to count weekdays between two dates. This gives you a basic business-day calculation. You may need additional holiday calendars for regional schedules, company closures, or custom working patterns. Also decide whether the visible bar should represent elapsed calendar time or working time, because those choices communicate different planning assumptions.

Conclusion

Creating a Gantt chart in Python involves four essentials: task labels, start dates, finish dates, and a timeline library. Plotly provides the quickest route to an interactive schedule, while Matplotlib gives you deeper control over static rendering.

Start with a small task list, convert dates carefully, reverse the task axis, and add milestones where a narrow bar could disappear. Then introduce status, owners, progress, and business-day calculations as your planning needs grow.

But here's the truth: a chart only helps when the underlying schedule stays current. Python is excellent for custom analysis and visualization. For ongoing team planning, ONES.com can provide a shared project environment with workflows, reporting, sprint management, automation, and flexible deployment options.

The solution is simple: use Python when you need a tailored visual analysis, and use a project workspace when people need to plan, update, and manage work together.