--- name: gantt-chart description: "Generate Gantt charts for construction scheduling. Create visual project timelines with dependencies and progress tracking." --- # Gantt Chart Generator ## Business Case ### Problem Statement Schedule visualization challenges: - Complex task dependencies - Progress tracking - Critical path visibility - Multi-level WBS display ### Solution Generate interactive Gantt charts from schedule data with dependency visualization, progress tracking, and export capabilities. ## Technical Implementation ```python import pandas as pd from typing import Dict, Any, List, Optional from dataclasses import dataclass, field from datetime import date, timedelta from enum import Enum class TaskStatus(Enum): NOT_STARTED = "not_started" IN_PROGRESS = "in_progress" COMPLETED = "completed" DELAYED = "delayed" ON_HOLD = "on_hold" class DependencyType(Enum): FS = "finish_to_start" SS = "start_to_start" FF = "finish_to_finish" SF = "start_to_finish" @dataclass class Task: task_id: str name: str start_date: date end_date: date wbs_code: str = "" progress: float = 0 # 0-100 status: TaskStatus = TaskStatus.NOT_STARTED assignee: str = "" level: int = 0 is_milestone: bool = False is_summary: bool = False parent_id: str = "" @dataclass class Dependency: predecessor_id: str successor_id: str dep_type: DependencyType = DependencyType.FS lag: int = 0 class GanttChartGenerator: """Generate Gantt charts for construction scheduling.""" def __init__(self, project_name: str): self.project_name = project_name self.tasks: Dict[str, Task] = {} self.dependencies: List[Dependency] = [] def add_task(self, task: Task): """Add task to chart.""" self.tasks[task.task_id] = task def add_dependency(self, predecessor_id: str, successor_id: str, dep_type: DependencyType = DependencyType.FS, lag: int = 0): """Add dependency between tasks.""" self.dependencies.append(Dependency( predecessor_id=predecessor_id, successor_id=successor_id, dep_type=dep_type, lag=lag )) def import_from_df(self, df: pd.DataFrame): """Import tasks from DataFrame.""" for _, row in df.iterrows(): task = Task( task_id=str(row['task_id']), name=row['name'], start_date=pd.to_datetime(row['start_date']).date(), end_date=pd.to_datetime(row['end_date']).date(), wbs_code=str(row.get('wbs_code', '')), progress=float(row.get('progress', 0)), level=int(row.get('level', 0)), is_milestone=bool(row.get('is_milestone', False)), is_summary=bool(row.get('is_summary', False)), parent_id=str(row.get('parent_id', '')) ) self.add_task(task) def get_project_range(self) -> tuple: """Get project date range.""" if not self.tasks: return (date.today(), date.today()) min_date = min(t.start_date for t in self.tasks.values()) max_date = max(t.end_date for t in self.tasks.values()) return (min_date, max_date) def get_duration(self, task_id: str) -> int: """Get task duration in days.""" task = self.tasks.get(task_id) if task: return (task.end_date - task.start_date).days + 1 return 0 def generate_text_gantt(self, width: int = 60) -> str: """Generate text-based Gantt chart.""" if not self.tasks: return "No tasks" lines = [] start, end = self.get_project_range() total_days = (end - start).days + 1 scale = width / total_days if total_days > 0 else 1 # Header lines.append(f"Project: {self.project_name}") lines.append(f"Period: {start} to {end}") lines.append("-" * (40 + width)) # Tasks for task in sorted(self.tasks.values(), key=lambda t: (t.level, t.start_date)): indent = " " * task.level name = f"{indent}{task.name}"[:35].ljust(35) # Bar position bar_start = int((task.start_date - start).days * scale) bar_length = max(1, int(self.get_duration(task.task_id) * scale)) # Progress bar progress_length = int(bar_length * task.progress / 100) bar = " " * bar_start bar += "█" * progress_length bar += "░" * (bar_length - progress_length) bar = bar[:width].ljust(width) status_char = "◆" if task.is_milestone else "│" lines.append(f"{name} {status_char}{bar}│ {task.progress:.0f}%") return "\n".join(lines) def generate_mermaid_gantt(self) -> str: """Generate Mermaid Gantt diagram.""" lines = [ "gantt", f" title {self.project_name}", " dateFormat YYYY-MM-DD", "" ] # Group by WBS prefix sections = {} for task in self.tasks.values(): section = task.wbs_code.split('.')[0] if task.wbs_code else "Tasks" if section not in sections: sections[section] = [] sections[section].append(task) for section, tasks in sections.items(): lines.append(f" section {section}") for task in sorted(tasks, key=lambda t: t.start_date): duration = self.get_duration(task.task_id) status = "" if task.status == TaskStatus.COMPLETED: status = "done, " elif task.status == TaskStatus.IN_PROGRESS: status = "active, " if task.is_milestone: lines.append(f" {task.name} :milestone, {task.start_date}, 0d") else: lines.append(f" {task.name} :{status}{task.task_id}, {task.start_date}, {duration}d") return "\n".join(lines) def generate_html_gantt(self) -> str: """Generate HTML/CSS Gantt chart.""" start, end = self.get_project_range() total_days = (end - start).days + 1 html = f""" Gantt Chart - {self.project_name}

{self.project_name}

{start} - {end}

""" for task in sorted(self.tasks.values(), key=lambda t: (t.level, t.start_date)): left = ((task.start_date - start).days / total_days) * 100 width = (self.get_duration(task.task_id) / total_days) * 100 progress_width = width * task.progress / 100 indent = " " * (task.level * 4) if task.is_milestone: html += f'
{indent}{task.name}
\n' else: html += f'''
{indent}{task.name}
\n''' html += "
" return html def get_critical_path(self) -> List[str]: """Identify critical path tasks (simplified).""" if not self.dependencies: return [t.task_id for t in sorted(self.tasks.values(), key=lambda x: x.end_date)[-5:]] # Find tasks with no slack (simplified approach) critical = [] _, project_end = self.get_project_range() for task in self.tasks.values(): if task.end_date == project_end: critical.append(task.task_id) # Trace predecessors for dep in self.dependencies: if dep.successor_id == task.task_id: critical.append(dep.predecessor_id) return list(set(critical)) def export_to_excel(self, output_path: str) -> str: """Export Gantt data to Excel.""" with pd.ExcelWriter(output_path, engine='openpyxl') as writer: # Tasks tasks_df = pd.DataFrame([{ 'ID': t.task_id, 'WBS': t.wbs_code, 'Name': t.name, 'Start': t.start_date, 'End': t.end_date, 'Duration': self.get_duration(t.task_id), 'Progress': t.progress, 'Status': t.status.value, 'Level': t.level } for t in self.tasks.values()]) tasks_df.to_excel(writer, sheet_name='Tasks', index=False) # Dependencies deps_df = pd.DataFrame([{ 'Predecessor': d.predecessor_id, 'Successor': d.successor_id, 'Type': d.dep_type.value, 'Lag': d.lag } for d in self.dependencies]) deps_df.to_excel(writer, sheet_name='Dependencies', index=False) return output_path ``` ## Quick Start ```python from datetime import date, timedelta # Create Gantt chart gantt = GanttChartGenerator("Office Building A") # Add tasks gantt.add_task(Task("T1", "Foundation", date(2024, 6, 1), date(2024, 6, 30), "01", progress=100)) gantt.add_task(Task("T2", "Structure", date(2024, 7, 1), date(2024, 9, 30), "02", progress=60)) gantt.add_task(Task("T3", "MEP Rough-in", date(2024, 8, 1), date(2024, 10, 31), "03", progress=30)) gantt.add_task(Task("M1", "Topping Out", date(2024, 9, 30), date(2024, 9, 30), is_milestone=True)) # Add dependencies gantt.add_dependency("T1", "T2") gantt.add_dependency("T2", "T3") # Generate text Gantt print(gantt.generate_text_gantt()) ``` ## Common Use Cases ### 1. Mermaid Diagram ```python mermaid = gantt.generate_mermaid_gantt() print(mermaid) # Copy to Mermaid editor ``` ### 2. HTML Export ```python html = gantt.generate_html_gantt() with open("gantt.html", "w") as f: f.write(html) ``` ### 3. Critical Path ```python critical = gantt.get_critical_path() print(f"Critical tasks: {critical}") ``` ## Resources - **DDC Book**: Chapter 3.3 - 4D Scheduling and BIM - **Website**: https://datadrivenconstruction.io