Curso
Neste passo a passo, vamos falar sobre o que é um gráfico de Gantt, por que e quando esse tipo de visualização é útil, como fazer um gráfico de Gantt em Python com matplotlib e como personalizá-lo. No caminho, vamos construir alguns exemplos de gráficos de Gantt em matplotlib.
O que é um gráfico de Gantt e por que ele é útil?
Um gráfico de Gantt é um tipo de gráfico de barras usado para ilustrar um cronograma completo de planejamento e gestão de projetos. Ele costuma exibir itens como nomes das tarefas do projeto, datas de início e término, times responsáveis por cada tarefa, status de conclusão em percentual ou fração, desempenho por time e marcos importantes do projeto.
Os nomes das tarefas ficam no eixo y do gráfico de Gantt, geralmente ordenados por data de início em ordem cronológica de cima para baixo. Já o eixo x representa períodos de tempo, e a largura de cada barra corresponde à duração de execução de cada tarefa.
Como um gráfico de Gantt é, essencialmente, uma forma específica de gráfico de barras horizontal, podemos criá-lo em várias bibliotecas de visualização de dados do Python, incluindo matplotlib, Plotly, Bokeh e Altair. Neste tutorial, vamos focar em criar gráficos de Gantt na mais popular delas – o matplotlib.
Se você quiser explorar outras maneiras de criar um gráfico de Gantt e outros tipos de gráficos em Python, confira o curso Introduction to Data Visualization with Plotly in Python. Se precisar revisar ou desenvolver suas habilidades em matplotlib, estes cursos vão ajudar: Introduction to Data Visualization with Matplotlib e Python for MATLAB Users.
Como fazer um gráfico de Gantt em Python com Matplotlib
Usando barh()
Vamos começar criando um gráfico de Gantt básico no matplotlib usando o método barh() de matplotlib.pyplot.
Primeiro, precisamos importar as bibliotecas necessárias:
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import datetime as dt
Em seguida, vamos criar dados de exemplo para trabalhar:
df = pd.DataFrame({'task': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'],
'team': ['R&D', 'Accounting', 'Sales', 'Sales', 'IT', 'R&D', 'IT', 'Sales', 'Accounting', 'Accounting', 'Sales', 'IT'],
'start': pd.to_datetime(['20 Oct 2022', '24 Oct 2022', '26 Oct 2022', '31 Oct 2022', '3 Nov 2022', '7 Nov 2022', '10 Nov 2022', '14 Nov 2022', '18 Nov 2022', '23 Nov 2022', '28 Nov 2022', '30 Nov 2022']),
'end': pd.to_datetime(['31 Oct 2022', '28 Oct 2022', '31 Oct 2022', '8 Nov 2022', '9 Nov 2022', '18 Nov 2022', '17 Nov 2022', '22 Nov 2022', '23 Nov 2022', '1 Dec 2022', '5 Dec 2022', '5 Dec 2022']),
'completion_frac': [1, 1, 1, 1, 1, 0.95, 0.7, 0.35, 0.1, 0, 0, 0]})
print(df)
Saída:
task team start end completion_frac
0 A R&D 2022-10-20 2022-10-31 1.00
1 B Accounting 2022-10-24 2022-10-28 1.00
2 C Sales 2022-10-26 2022-10-31 1.00
3 D Sales 2022-10-31 2022-11-08 1.00
4 E IT 2022-11-03 2022-11-09 1.00
5 F R&D 2022-11-07 2022-11-18 0.95
6 G IT 2022-11-10 2022-11-17 0.70
7 H Sales 2022-11-14 2022-11-22 0.35
8 I Accounting 2022-11-18 2022-11-23 0.10
9 J Accounting 2022-11-23 2022-12-01 0.00
10 K Sales 2022-11-28 2022-12-05 0.00
11 L IT 2022-11-30 2022-12-05 0.00
Para facilitar a construção do gráfico de Gantt com esses dados, precisamos calcular mais quatro colunas que representarão as seguintes informações:
- Quantos dias se passaram/passariam desde o início geral do projeto até a data de início de cada tarefa:
df['days_to_start'] = (df['start'] - df['start'].min()).dt.days
- Quantos dias se passaram/passariam desde o início geral do projeto até a data de término de cada tarefa:
df['days_to_end'] = (df['end'] - df['start'].min()).dt.days
- A duração de cada tarefa, incluindo as datas de início e de término:
df['task_duration'] = df['days_to_end'] - df['days_to_start'] + 1 # para incluir também a data de término
- O status de conclusão de cada tarefa convertido de fração para uma quantidade de dias alocados à tarefa:
df['completion_days'] = df['completion_frac'] * df['task_duration']
Vamos conferir o que obtivemos:
print(df)
Saída:
task team start end completion_frac days_to_start \
0 A R&D 2022-10-20 2022-10-31 1.00 0
1 B Accounting 2022-10-24 2022-10-28 1.00 4
2 C Sales 2022-10-26 2022-10-31 1.00 6
3 D Sales 2022-10-31 2022-11-08 1.00 11
4 E IT 2022-11-03 2022-11-09 1.00 14
5 F R&D 2022-11-07 2022-11-18 0.95 18
6 G IT 2022-11-10 2022-11-17 0.70 21
7 H Sales 2022-11-14 2022-11-22 0.35 25
8 I Accounting 2022-11-18 2022-11-23 0.10 29
9 J Accounting 2022-11-23 2022-12-01 0.00 34
10 K Sales 2022-11-28 2022-12-05 0.00 39
11 L IT 2022-11-30 2022-12-05 0.00 41
days_to_end task_duration completion_days
0 11 12 12.00
1 8 5 5.00
2 11 6 6.00
3 19 9 9.00
4 20 7 7.00
5 29 12 11.40
6 28 8 5.60
7 33 9 3.15
8 34 6 0.60
9 42 9 0.00
10 46 8 0.00
11 46 6 0.00
Agora, estamos prontos para gerar um gráfico de Gantt básico no matplotlib:
plt.barh(y=df['task'], width=df['task_duration'], left=df['days_to_start'])
plt.show()
Saída:

Esse gráfico ainda precisa de alguns ajustes para extrair o máximo de informação:
- Adicionar um título relevante e rótulos no eixo x (datas)
- Inverter o eixo y para ter as tarefas em ordem cronológica de cima para baixo
- Adicionar uma grade
- Colorir as tarefas por time
- Exibir uma legenda
- Adicionar o status de conclusão das tarefas
Vamos implementar tudo isso já já. Antes, porém, vamos ver outra forma de construir um gráfico de Gantt básico.
Usando broken_barh()
Quando algumas tarefas têm duas ou mais subtarefas distribuídas ao longo do tempo, devemos usar outro método do matplotlib – broken_barh().
Para ter dados de exemplo e testar essa abordagem, vamos fazer o seguinte:
- Pegar do nosso dataframe inicial apenas as tarefas atribuídas ao time de Sales (por enquanto, vamos ignorar a coluna
completion_frac). - Renomear as colunas
starteendparastart_1eend_1, respectivamente (por conveniência). - Adicionar mais subtarefas a algumas das tarefas disponíveis.
- Para cada subtarefa, calcular as três colunas mostradas acima representando:
- Quantos dias se passaram/passariam desde o início geral do projeto até a data de início de cada subtarefa.
- Quantos dias se passaram/passariam desde o início geral do projeto até a data de término de cada subtarefa.
- A duração de cada subtarefa, incluindo as datas de início e de término.
# 1
df2 = df[df['team']=='Sales'][['task', 'team', 'start', 'end']]
# 2
df2.rename(columns={'start': 'start_1', 'end': 'end_1'}, inplace=True)
df2.reset_index(drop=True, inplace=True)
# 3
df2['start_2'] = pd.to_datetime([None, '10 Nov 2022', '25 Nov 2022', None])
df2['end_2'] = pd.to_datetime([None, '14 Nov 2022', '28 Nov 2022', None])
df2['start_3'] = pd.to_datetime([None, None, '1 Dec 2022', None])
df2['end_3'] = pd.to_datetime([None, None, '5 Dec 2022', None])
# 4
for i in [1, 2, 3]:
suffix = '_' + str(i)
df2['days_to_start' + suffix] = (df2['start' + suffix] - df2['start_1'].min()).dt.days
df2['days_to_end' + suffix] = (df2['end' + suffix] - df2['start_1'].min()).dt.days
df2['task_duration' + suffix] = df2['days_to_end' + suffix] - df2['days_to_start' + suffix] + 1
print(df2)
Saída:
task team start_1 end_1 start_2 end_2 start_3 \
0 C Sales 2022-10-26 2022-10-31 NaT NaT NaT
1 D Sales 2022-10-31 2022-11-08 2022-11-10 2022-11-14 NaT
2 H Sales 2022-11-14 2022-11-22 2022-11-25 2022-11-28 2022-12-01
3 K Sales 2022-11-28 2022-12-05 NaT NaT NaT
end_3 days_to_start_1 days_to_end_1 task_duration_1 \
0 NaT 0 5 6
1 NaT 5 13 9
2 2022-12-05 19 27 9
3 NaT 33 40 8
days_to_start_2 days_to_end_2 task_duration_2 days_to_start_3 \
0 NaN NaN NaN NaN
1 15.0 19.0 5.0 NaN
2 30.0 33.0 4.0 36.0
3 NaN NaN NaN NaN
days_to_end_3 task_duration_3
0 NaN NaN
1 NaN NaN
2 40.0 5.0
3 NaN NaN
Repare que agora a tarefa D tem duas subtarefas, a tarefa H tem três subtarefas e as tarefas C e K têm uma subtarefa cada. Portanto, D e H são as tarefas para as quais vamos aplicar o método broken_barh(). Como a sintaxe desse método é um pouco menos intuitiva que a do barh(), vamos ver seus principais parâmetros e formatos. Os parâmetros obrigatórios de broken_barh() são:
xranges– uma sequência de tuplas no formato (xmin,xwidth) para indicar o início e a extensão de cada barra. Aqui, cada barra representa uma subtarefa. Ou seja, esse parâmetro exibe a data de início e a duração de cada subtarefa.yrange– uma tupla no formato (ymin,yheight) para indicar a posição em y e a altura de cada barra.
Agora, vamos plotar nosso gráfico de Gantt "quebrado". O passo a passo é:
- Criar uma figura com subplots.
- Iterar pelas linhas do dataframe e verificar se a tarefa tem uma, duas ou três subtarefas. Com base nisso, fazer o seguinte:
- Uma subtarefa: plotar uma barra usando o método
barh(), como fizemos antes. - Duas subtarefas: plotar duas barras usando
broken_barh(). - Três subtarefas: plotar três barras usando
broken_barh(). - Adicionar ajustes básicos: definir e rotular os ticks do eixo y.
# 1
fig, ax = plt.subplots()
# 2
for index, row in df2.iterrows():
if row['start_2'] is None:
ax.barh(y=df2['task'], width=df2['task_duration_1'], left=df2['days_to_start_1'])
elif row['start_2'] is not None and row['start_3'] is None:
ax.broken_barh(xranges=[(row['days_to_start_1'], row['task_duration_1']), (row['days_to_start_2'], row['task_duration_2'])], yrange=(index + 1, 0.5))
else:
ax.broken_barh(xranges=[(row['days_to_start_1'], row['task_duration_1']), (row['days_to_start_2'], row['task_duration_2']), (row['days_to_start_3'], row['task_duration_3'])], yrange=(index + 1, 0.5))
# 3
ax.set_yticks([1.25, 2.25, 3.25, 4.25])
ax.set_yticklabels(df2['task'])
plt.show()
Saída:

No código acima, também podemos usar o método broken_barh() para plotar as barras das tarefas com apenas uma subtarefa. Nesse caso, basta substituir esta linha:
ax.barh(y=df2['task'], width=df2['task_duration_1'], left=df2['days_to_start_1'])
por esta:
ax.broken_barh(xranges=[(row['days_to_start_1'], row['task_duration_1'])], yrange=(index + 1, 0.5))
Essa abordagem também deixa o código mais uniforme e legível.
Como personalizar um gráfico de Gantt no Matplotlib
Vamos voltar ao nosso dataframe inicial:
print(df)
Saída:
task team start end completion_frac days_to_start \
0 A R&D 2022-10-20 2022-10-31 1.00 0
1 B Accounting 2022-10-24 2022-10-28 1.00 4
2 C Sales 2022-10-26 2022-10-31 1.00 6
3 D Sales 2022-10-31 2022-11-08 1.00 11
4 E IT 2022-11-03 2022-11-09 1.00 14
5 F R&D 2022-11-07 2022-11-18 0.95 18
6 G IT 2022-11-10 2022-11-17 0.70 21
7 H Sales 2022-11-14 2022-11-22 0.35 25
8 I Accounting 2022-11-18 2022-11-23 0.10 29
9 J Accounting 2022-11-23 2022-12-01 0.00 34
10 K Sales 2022-11-28 2022-12-05 0.00 39
11 L IT 2022-11-30 2022-12-05 0.00 41
days_to_end task_duration completion_days
0 11 12 12.00
1 8 5 5.00
2 11 6 6.00
3 19 9 9.00
4 20 7 7.00
5 29 12 11.40
6 28 8 5.60
7 33 9 3.15
8 34 6 0.60
9 42 9 0.00
10 46 8 0.00
11 46 6 0.00
e ao nosso primeiro gráfico de Gantt:
plt.barh(y=df['task'], width=df['task_duration'], left=df['days_to_start'])
plt.show()
Saída:

Em particular, queremos adicionar algumas personalizações para tornar o gráfico mais informativo.
Para se aprofundar na personalização de gráficos com matplotlib, confira: Matplotlib Tutorial: Python Plotting e a Matplotlib Cheat Sheet: Plotting in Python.
Ajustes básicos no gráfico de Gantt
Incluem:
- Adicionar um título ao gráfico
- Ajustar e personalizar os eixos
- Adicionar uma grade
Adicionando um título
Ter um título claro é essencial para qualquer gráfico, inclusive um gráfico de Gantt. Vamos assumir que nosso projeto se chama Projeto X:
plt.barh(y=df['task'], width=df['task_duration'], left=df['days_to_start'])
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.show()
Saída:

Ajustando e personalizando os eixos e adicionando uma grade
Nesta etapa, vamos partir do código anterior e fazer o seguinte:
- Criar uma figura com subplots para trabalhar com o objeto
ax. - Inverter o eixo y para ter todas as tarefas em ordem cronológica de cima para baixo.
- Definir as posições ideais dos ticks do eixo x.
- Digamos que queremos posicionar os ticks do eixo x em todas as segundas-feiras, lembrando que o Projeto X começou em uma quinta-feira.
- Vamos manter apenas os major ticks para evitar poluição visual.
- Definir os rótulos corretos dos ticks do eixo x e seu formato.
- De novo, lembrando que o Projeto X começou em uma quinta-feira e que precisamos rotular apenas as segundas-feiras.
- Como o Projeto X está todo em 2022, não precisamos indicar o ano em cada data.
- É importante garantir que a quantidade de ticks no eixo x seja igual à de seus rótulos.
- Adicionar os ticks e rótulos do eixo x.
- Adicionar uma grade vertical.
# 1
fig, ax = plt.subplots()
plt.barh(y=df['task'], width=df['task_duration'], left=df['days_to_start'] + 1)
plt.title('Project Management Schedule of Project X', fontsize=15)
# 2
plt.gca().invert_yaxis()
# 3
xticks = np.arange(5, df['days_to_end'].max() + 2, 7)
# 4
xticklabels = pd.date_range(start=df['start'].min() + dt.timedelta(days=4), end=df['end'].max()).strftime("%d/%m")
# 5
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
# 6
ax.xaxis.grid(True, alpha=0.5)
plt.show()
Saída:

Se você precisa de mais prática para ler e escrever qualquer código em Python com tranquilidade, o curso Intermediate Python é um ótimo ponto de partida.
Colorindo tarefas por time
Agora, vamos colorir cada barra (que representa uma tarefa) de acordo com o time responsável. Para isso, vamos:
- Criar um dicionário com os nomes dos times como chaves e cores básicas do matplotlib como valores.
- Criar uma figura com subplots.
- Iterar pelas linhas do dataframe, criar uma barra para cada linha e aplicar a cor correspondente ao time.
- Adicionar todos os ajustes básicos que aplicamos antes.
# 1
team_colors = {'R&D': 'c', 'Accounting': 'm', 'Sales': 'y', 'IT': 'b'}
# 2
fig, ax = plt.subplots()
# 3
for index, row in df.iterrows():
plt.barh(y=row['task'], width=row['task_duration'], left=row['days_to_start'] + 1, color=team_colors[row['team']])
# 4
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.gca().invert_yaxis()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
ax.xaxis.grid(True, alpha=0.5)
plt.show()
Saída:

Adicionando uma legenda
Falta no gráfico acima uma legenda que mostre qual cor corresponde a qual time. Para resolver, vamos criar patches – objetos 2D do matplotlib preenchidos com uma cor definida. Usaremos a classe matplotlib.patches.Patch para gerar uma lista de patches, um para cada time, com a cor apropriada:
patches = []
for team in team_colors:
patches.append(matplotlib.patches.Patch(color=team_colors[team]))
Agora, podemos adicionar a legenda ao gráfico:
fig, ax = plt.subplots()
for index, row in df.iterrows():
plt.barh(y=row['task'], width=row['task_duration'], left=row['days_to_start'] + 1, color=team_colors[row['team']])
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.gca().invert_yaxis()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
ax.xaxis.grid(True, alpha=0.5)
# Adicionando uma legenda
ax.legend(handles=patches, labels=team_colors.keys(), fontsize=11)
plt.show()
Saída:

O gráfico de Gantt acima ficou bem mais informativo.
Você vai encontrar mais técnicas para melhorar a estética e a legibilidade dos gráficos no curso Improving Your Data Visualizations in Python.
Adicionando o status de conclusão
Até agora, não usamos a coluna completion_days que calculamos no início do tutorial. Vamos usá-la agora para deixar o gráfico mais claro, mostrando em que estágio de conclusão cada tarefa está.
Tecnicamente, vamos adicionar outra barra por cima de cada barra já construída. A barra inferior representa a duração total da tarefa em dias alocados, enquanto a superior corresponde ao status de conclusão dessa tarefa em dias alocados.
Para cada tarefa, as duas barras terão a mesma cor do time correspondente, mas a barra inferior será mais clara que a superior para destacar a diferença entre elas.
Em outras palavras, se um time estiver acima do ritmo em uma tarefa, a combinação das duas barras vai parecer que o time já trabalhou mais dias do que o esperado (indicando que a tarefa provavelmente será concluída antes do prazo).
No caso oposto, se um time estiver abaixo do ritmo, a combinação das barras vai mostrar que o time trabalhou menos dias que o esperado (indicando que a tarefa provavelmente será concluída após o prazo).
fig, ax = plt.subplots()
for index, row in df.iterrows():
# Barra inferior - duração total da tarefa
plt.barh(y=row['task'], width=row['task_duration'], left=row['days_to_start'] + 1, color=team_colors[row['team']], alpha=0.4)
# Barra superior - status de conclusão
plt.barh(y=row['task'], width=row['completion_days'], left=row['days_to_start'] + 1, color=team_colors[row['team']])
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.gca().invert_yaxis()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
ax.xaxis.grid(True, alpha=0.5)
ax.legend(handles=patches, labels=team_colors.keys(), fontsize=11)
plt.show()
Saída:

No gráfico acima, dá para ver que os times não avançam no mesmo ritmo — algo comum em projetos reais. Para extrair mais informação, vamos supor que a data atual é 17 de novembro de 2022 e marcá-la no gráfico:
fig, ax = plt.subplots()
for index, row in df.iterrows():
plt.barh(y=row['task'], width=row['task_duration'], left=row['days_to_start'] + 1, color=team_colors[row['team']], alpha=0.4)
plt.barh(y=row['task'], width=row['completion_days'], left=row['days_to_start'] + 1, color=team_colors[row['team']])
plt.title('Project Management Schedule of Project X', fontsize=15)
plt.gca().invert_yaxis()
ax.set_xticks(xticks)
ax.set_xticklabels(xticklabels[::7])
ax.xaxis.grid(True, alpha=0.5)
ax.legend(handles=patches, labels=team_colors.keys(), fontsize=11)
# Marcando a data atual no gráfico
ax.axvline(x=29, color='r', linestyle='dashed')
ax.text(x=29.5, y=11.5, s='17/11', color='r')
plt.show()
Saída:

Podemos fazer as seguintes observações sobre o status atual do Projeto X:
- O time de R&D está acima do ritmo na tarefa F e provavelmente a concluirá antes do prazo.
- O time de IT está atrasado na tarefa G e provavelmente precisa de suporte.
- O time de Sales está seguindo o cronograma na tarefa H.
- O time de Accounting começou a tarefa I adiantado em relação ao cronograma do projeto.
Essas observações ajudam líderes de time e o gerente do projeto a ajustar o cronograma e alocar recursos a tempo para evitar atrasos nas entregas.
Outros possíveis ajustes
Podemos adicionar mais detalhes ao nosso gráfico de Gantt no matplotlib. Por exemplo:
- Adicionar o percentual de conclusão em cada barra.
- Subdividir algumas tarefas em subtarefas (já vimos como fazer isso tecnicamente mais acima).
- Anotar tarefas correlacionadas.
- Destacar marcos e prazos críticos.
- Realçar fins de semana e feriados.
- Ordenar as barras por time ou tipo de tarefa.
- Adicionar minor ticks no eixo x para aumentar a granularidade de informação.
- Melhorar a formatação geral e o layout do gráfico.
É importante, no entanto, manter o gráfico de Gantt limpo e evitar excesso de detalhes. Como em qualquer visualização de dados, precisamos equilibrar a quantidade de informação com a legibilidade do gráfico e também do código usado para construí-lo.
Comece a criar seu próprio gráfico de Gantt em Python com Matplotlib
Para resumir, neste tutorial, exploramos:
- o que é um gráfico de Gantt
- que tipo de informação ele exibe
- quando ele é usado
- as diferentes formas e nuances de criar gráficos de Gantt com matplotlib
- como fazer um gráfico de Gantt em Python com matplotlib
- como ajustá-lo para deixá-lo mais informativo
- como interpretar o resultado final.
Além disso, vimos vários exemplos de gráficos de Gantt e o passo a passo para construí-los.
Para uma abordagem mais integrada de como criar gráficos impactantes e informativos em Python, confira nossa trilha de habilidades Data Visualization with Python.


