Chris Parmer — home

Gantt chart

fabricated software project with multicategory team/task axes

Example from the compendium of canonical charts

Gantt chart — fabricated software project with multicategory team/task axes

Python Code

"""Gantt chart — fabricated software project with multicategory team/task axes."""


import pandas as pd
import numpy as np
import plotly.graph_objects as go


# Fabricated 10-week sprint plan
# [team, task_name, start_day, end_day, color_key]
TASKS = [
    # Team Alpha
    ("Team Alpha", "Task 1 · Requirements",    0,  7,  VIOLET),
    ("Team Alpha", "Task 2 · Architecture",    5, 14,  VIOLET),
    ("Team Alpha", "Task 3 · Backend Build",  12, 35,  VIOLET),
    ("Team Alpha", "Task 4 · Integration",    30, 42,  VIOLET),
    # Team Beta
    ("Team Beta",  "Task 5 · UI Design",       3, 18,  GREEN),
    ("Team Beta",  "Task 6 · Frontend Build", 15, 35,  GREEN),
    ("Team Beta",  "Task 7 · QA Testing",     32, 45,  GREEN),
    # Team Gamma
    ("Team Gamma", "Task 8 · Infra Setup",     0, 10,  TEAL),
    ("Team Gamma", "Task 9 · CI/CD Pipeline",  8, 20,  TEAL),
    ("Team Gamma", "Task 10 · Deploy & Monitor",40, 50, TEAL),
]

START_DATE = pd.Timestamp("2025-01-06")  # Monday


def generate():
    print("building fabricated multicategory Gantt chart …")

    rows = []
    for team, task, d0, d1, color in TASKS:
        rows.append({
            "team": team,
            "task": task,
            "start": START_DATE + pd.Timedelta(days=d0),
            "end":   START_DATE + pd.Timedelta(days=d1),
            "color": color,
        })
    df = pd.DataFrame(rows)

    # Multicategory y-axis: [team, task] pairs (task 1 at top → reversed order)
    y_labels = [[row["team"], row["task"]] for _, row in df.iterrows()]

    fig = go.Figure()

    # One trace per team for legend grouping
    teams = ["Team Alpha", "Team Beta", "Team Gamma"]
    team_colors = [VIOLET, GREEN, TEAL]

    for team, color in zip(teams, team_colors):
        sub = df[df["team"] == team]
        for _, row in sub.iterrows():
            duration_ms = (row["end"] - row["start"]).total_seconds() * 1000
            fig.add_trace(go.Bar(
                x=[duration_ms],
                y=[[row["team"], row["task"]]],
                base=[row["start"].value // 10**6],  # ms timestamp
                orientation="h",
                marker=dict(color=color, opacity=0.85,
                            line=dict(color="white", width=1.5)),
                name=team,
                legendgroup=team,
                showlegend=bool(row.name == sub.index[0]),  # show once per team
                hovertemplate=(
                    f"<b>{row['task']}</b><br>"
                    f"{row['start'].strftime('%b %d')} – {row['end'].strftime('%b %d')}<br>"
                    f"{(row['end'] - row['start']).days} days<extra></extra>"
                ),
            ))

    # Milestone marker: launch day
    launch = START_DATE + pd.Timedelta(days=50)
    fig.add_vline(
        x=launch.value // 10**6,
        line=dict(color=PINK, width=2, dash="dash"),
        annotation_text="Launch",
        annotation_position="top",
        annotation_font=dict(size=11, color=PINK),
    )

    fig.update_layout(
        barmode="overlay",
        xaxis=dict(
            title="",
            type="date",
            showgrid=True,
            dtick="M1",
            tickformat="%b %d",
        ),
        yaxis=dict(
            autorange="reversed",
            type="multicategory",
            showgrid=True,
            gridcolor="#e8e8f0",
        ),
        legend=dict(orientation="h", y=1.08),
        bargap=0.25,
        margin=dict(t=50, b=50, l=20, r=40),
        height=480,
    )
    return fig


if __name__ == "__main__":
    generate()

Made with Plotly