import streamlit as st import matplotlib.pyplot as plt import numpy as np import pandas as pd import matplotlib.dates as mdates import seaborn as sns from io import BytesIO from datetime import datetime, timedelta import plotly.graph_objects as go import plotly.express as px from plotly.subplots import make_subplots from matplotlib.colors import LinearSegmentedColormap from wordcloud import WordCloud # Streamlit app title and tab configuration st.set_page_config(page_title="Project Timeline Manager", layout="wide") st.title("Project Timeline Management App") # Create tabs for different chart types tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([ "Progress Bar Chart", "Timeline Chart", "Progress Pie Chart", "Gantt Chart", "Resource Allocation", "Executive Dashboard" ]) # Sidebar project details st.sidebar.header("Project Details") project_name = st.sidebar.text_input("Project Name", "Sample Project", key="project_name") total_duration = st.sidebar.slider("Total Project Duration (Months)", 1, 24, 12, key="total_duration") # Project start date for Gantt chart start_date = st.sidebar.date_input("Project Start Date", datetime.now(), key="start_date") # Number of tasks task_count = st.sidebar.number_input("Number of Tasks", min_value=1, max_value=10, value=3, key="task_count") tasks = [] task_colors = [] timeline_tasks = [] gantt_tasks = [] resource_tasks = [] # Font Properties - Common for all charts st.sidebar.header("Font Settings") font_properties = { 'axis_label_size': st.sidebar.slider("Axis Label Font Size", 8, 20, 12, key="axis_label_size"), 'tick_size': st.sidebar.slider("Tick Font Size", 6, 20, 10, key="tick_size"), 'title_size': st.sidebar.slider("Title Font Size", 10, 24, 14, key="title_size"), 'legend_size': st.sidebar.slider("Legend Font Size", 8, 20, 12, key="legend_size") } # Set font to Times New Roman plt.rcParams['font.family'] = 'Times New Roman' # Theme selection theme_options = ["Default", "Business", "Modern", "Classic", "Vibrant"] selected_theme = st.sidebar.selectbox("Color Theme", theme_options, key="color_theme") # Theme color palettes theme_colors = { "Default": ["#4e79a7", "#f28e2c", "#e15759", "#76b7b2", "#59a14f", "#edc949", "#af7aa1", "#ff9da7", "#9c755f", "#bab0ab"], "Business": ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5"], "Modern": ["#636EFA", "#EF553B", "#00CC96", "#AB63FA", "#FFA15A", "#19D3F3", "#FF6692", "#B6E880", "#FF97FF", "#FECB52"], "Classic": ["#5A8CA8", "#F2A45C", "#8ABD5A", "#C15B5B", "#9D7ABD", "#7EC9C3", "#D67AB1", "#FDC55B", "#9E765F", "#B3B3B3"], "Vibrant": ["#ff3f3f", "#ffa51f", "#faff1f", "#1fff49", "#1fcaff", "#881fff", "#ff1fca", "#8b8b8b", "#ff871f", "#1fffb6"] } current_theme = theme_colors[selected_theme] # Helper Functions (MOVED HERE TO FIX THE ERROR) def adjust_color_brightness(hex_color, factor): """Adjust the brightness of a hex color by a factor.""" # Convert hex to RGB if hex_color.startswith('#'): hex_color = hex_color[1:] rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) # Adjust brightness rgb_adjusted = [min(255, int(c * factor)) for c in rgb] # Convert back to hex return f"#{rgb_adjusted[0]:02x}{rgb_adjusted[1]:02x}{rgb_adjusted[2]:02x}" # Task details collection for i in range(task_count): with st.sidebar.expander(f"Task {i+1} Details"): # Common task details task_name = st.text_input(f"Task {i+1} Name", f"Task {i+1}", key=f"task_name_{i}") # Progress Bar Chart details expected_duration = st.slider( f"Expected Duration (Months)", min_value=0.5, # Min value as float max_value=float(total_duration), # Max value as float value=min(3.0, float(total_duration)), # Default value as float step=0.5, # Step as float key=f"expected_duration_{i}" ) completed_percentage = st.slider( f"Completion Percentage", min_value=0, max_value=100, value=30, key=f"completion_percentage_{i}" ) # Use theme colors or custom colors use_theme_color = st.checkbox("Use Theme Colors", True, key=f"use_theme_color_{i}") if use_theme_color: task_color = current_theme[i % len(current_theme)] completed_color = adjust_color_brightness(task_color, 0.7) # Lighter version else: task_color = st.color_picker(f"Bar Color", f"#{np.random.randint(0, 0xFFFFFF):06x}", key=f"task_color_{i}") completed_color = st.color_picker(f"Completion Color", f"#{np.random.randint(0, 0xFFFFFF):06x}", key=f"completed_color_{i}") # Timeline Chart details start_month = st.selectbox(f"Start Month", list(range(1, 13)), index=min(i, 11), key=f"start_month_{i}") end_month = st.selectbox(f"End Month", list(range(1, 13)), index=min(i+2, 11), key=f"end_month_{i}") duration = end_month - start_month + 1 # Gantt Chart details task_start_date = start_date + timedelta(days=30*start_month) task_end_date = start_date + timedelta(days=30*end_month) # Resource allocation details resource_name = st.text_input(f"Resource Owner", f"Resource {i+1}", key=f"resource_name_{i}") resource_allocation = st.slider(f"Resource Allocation (%)", 0, 100, 50, key=f"resource_allocation_{i}") priority = st.selectbox(f"Priority", ["Low", "Medium", "High"], index=1, key=f"priority_{i}") # Dependencies (for Gantt chart) if i > 0: depends_on = st.multiselect(f"Depends on", [f"Task {j+1}" for j in range(i)], key=f"depends_on_{i}") else: depends_on = [] # Risk level risk_level = st.selectbox(f"Risk Level", ["Low", "Medium", "High"], index=0, key=f"risk_level_{i}") # Add to respective task lists tasks.append((task_name, expected_duration, completed_percentage)) task_colors.append((task_color, completed_color)) timeline_tasks.append((task_name, start_month, end_month, duration, completed_percentage)) gantt_tasks.append((task_name, task_start_date, task_end_date, completed_percentage, depends_on, risk_level)) resource_tasks.append((task_name, resource_name, resource_allocation, priority, risk_level)) # Progress Bar Chart Customization with st.sidebar.expander("Progress Bar Chart Settings"): bar_width = st.slider("Bar Width", 0.1, 1.0, 0.6, 0.1, key="bar_width") completion_bar_width = st.slider("Completion Bar Width", 0.1, 1.0, 0.4, 0.1, key="completion_bar_width") progress_text_position = st.selectbox("Completion % Position", ["on the bar", "left side of the bar", "right side of the bar"], key="progress_text_position") progress_text_size = st.slider("Progress Text Size", 6, 20, 10, key="progress_text_size") show_grid = st.checkbox("Show Grid", True, key="show_grid") grid_style = st.selectbox("Grid Style", ["solid", "dashed", "dotted", "dashdot"], key="grid_style") show_critical_path = st.checkbox("Highlight Critical Path", False, key="show_critical_path") # Timeline Chart Customization with st.sidebar.expander("Timeline Chart Settings"): timeline_bar_width = st.slider("Timeline Bar Width (%)", 50, 100, 80, 5, key="timeline_bar_width") / 100 show_milestones = st.checkbox("Show Milestones", True, key="show_milestones") milestone_style = st.selectbox("Milestone Style", ["star", "diamond", "triangle", "circle"], key="milestone_style") show_today_line = st.checkbox("Show Today Line", True, key="show_today_line") today_line_style = st.selectbox("Today Line Style", ["solid", "dashed", "dotted"], key="today_line_style") # Pie Chart Customization with st.sidebar.expander("Pie Chart Settings"): # Donut chart settings gap_size = st.slider("Gap Between Donuts", 0.0, 0.2, 0.1, key="gap_size") border_line = st.color_picker("Border Line Color", "#000000", key="border_line") show_inner_pie = st.checkbox("Show Inner Pie Chart", True, key="show_inner_pie") show_outer_pie = st.checkbox("Show Outer Pie Chart", True, key="show_outer_pie") pie_text_size = st.slider("Pie Chart Text Size", 6, 20, 12, key="pie_text_size") pie_text_orientation = st.selectbox("Text Orientation", ["font print in horizental", "font print in vertical", "font print in circular"], key="pie_text_orientation") explode_largest = st.checkbox("Explode Largest Segment", False, key="explode_largest") use_3d_pie = st.checkbox("Use 3D Pie", False, key="use_3d_pie") # Gantt Chart Customization with st.sidebar.expander("Gantt Chart Settings"): gantt_height = st.slider("Chart Height", 300, 800, 500, key="gantt_height") show_dependencies = st.checkbox("Show Dependencies", True, key="show_dependencies") critical_path_color = st.color_picker("Critical Path Color", "#FF0000", key="critical_path_color") weekend_color = st.color_picker("Weekend Highlight", "#f0f0f0", key="weekend_color") milestone_color = st.color_picker("Milestone Color", "#FFD700", key="milestone_color") show_resource_labels = st.checkbox("Show Resource Labels", True, key="show_resource_labels") risk_highlighting = st.checkbox("Highlight Risks", True, key="risk_highlighting") # Resource Allocation Chart Settings with st.sidebar.expander("Resource Allocation Settings"): resource_chart_type = st.selectbox("Chart Type", ["Stacked Bar", "Heatmap", "Bubble Chart"], key="resource_chart_type") show_overallocation = st.checkbox("Highlight Overallocation", True, key="show_overallocation") overallocation_threshold = st.slider("Overallocation Threshold (%)", 80, 120, 100, key="overallocation_threshold") group_by = st.selectbox("Group Resources By", ["None", "Priority", "Risk Level"], key="group_by") # Executive Dashboard Settings with st.sidebar.expander("Executive Dashboard Settings"): kpi_columns = st.slider("KPI Columns", 2, 4, 3, key="kpi_columns") show_word_cloud = st.checkbox("Show Word Cloud", True, key="show_word_cloud") show_burndown = st.checkbox("Show Burndown Chart", True, key="show_burndown") show_velocity = st.checkbox("Show Velocity Chart", True, key="show_velocity") velocity_periods = st.slider("Velocity Periods", 2, 10, 5, key="velocity_periods") burndown_style = st.selectbox("Burndown Style", ["Linear", "Ideal", "Custom"], key="burndown_style") dashboard_layout = st.selectbox("Dashboard Layout", ["Grid", "Vertical", "Horizontal"], key="dashboard_layout") # Legend Position (common for all charts) legend_position_options = ["top right", "top middle", "top left", "mid right", "mid middle", "mid left", "bottom right", "bottom middle", "bottom left"] legend_position = st.sidebar.selectbox("Legend Position", legend_position_options, key="legend_position") # Map legend positions to matplotlib legend_loc_map = { "top right": "upper right", "top middle": "upper center", "top left": "upper left", "mid right": "center right", "mid middle": "center", "mid left": "center left", "bottom right": "lower right", "bottom middle": "lower center", "bottom left": "lower left" } # Custom bbox_to_anchor values to slightly adjust the legend position legend_bbox_map = { "top right": (0.9, 1.1), "top middle": (0.5, 1.1), "top left": (0.1, 1.1), # Shifted higher "mid right": (1.1, 0.5), "mid middle": (0.5, 0.6), "mid left": (-0.1, 0.5), # Adjusted for clarity "bottom right": (0.9, -0.1), "bottom middle": (0.5, -0.1), "bottom left": (0.1, -0.1) # Shifted lower } # Brochure and Presentation Export Options with st.sidebar.expander("Export Options"): export_format = st.selectbox("Export Format", ["PNG", "PDF", "SVG", "HTML"], key="export_format") export_dpi = st.slider("Export DPI", 72, 600, 300, key="export_dpi") include_header = st.checkbox("Include Header/Footer", True, key="include_header") include_logo = st.checkbox("Include Logo", False, key="include_logo") page_size = st.selectbox("Page Size", ["Letter", "A4", "A3", "Legal"], key="page_size") orientation = st.selectbox("Orientation", ["Landscape", "Portrait"], key="orientation") # Function to create a dual-bar chart for each task def create_dual_bar_chart(tasks, bar_width, task_colors, common_x_range, font_properties, completion_bar_width, progress_text_position, progress_text_size): fig, ax = plt.subplots(figsize=(10, 6)) for i, (task_name, expected_duration, completed_percentage) in enumerate(tasks): task_color = task_colors[i][0] completed_color = task_colors[i][1] # Dark bar for expected duration ax.barh(i, expected_duration, color=task_color, height=bar_width, label='Expected Duration' if i == 0 else "") # Light bar for completed percentage with adjustable width completed_width = expected_duration * (completed_percentage / 100) ax.barh(i, completed_width, color=completed_color, height=completion_bar_width, label='Completed Percentage' if i == 0 else "") # Add completion percentage text if progress_text_position == "on the bar": ax.text(completed_width/2, i, f"{completed_percentage}%", ha='center', va='center', fontsize=progress_text_size, fontname='Times New Roman', color='black' if completed_percentage < 50 else 'white') elif progress_text_position == "left side of the bar": ax.text(0 - 0.5, i, f"{completed_percentage}%", ha='right', va='center', fontsize=progress_text_size, fontname='Times New Roman') elif progress_text_position == "right side of the bar": ax.text(expected_duration + 0.5, i, f"{completed_percentage}%", ha='left', va='center', fontsize=progress_text_size, fontname='Times New Roman') # Add grid if enabled if show_grid: ax.grid(True, linestyle=grid_style, alpha=0.7) # Highlight critical path if enabled if show_critical_path: # Find critical tasks (for demo, we'll consider the longest tasks as critical) critical_tasks = sorted(enumerate(tasks), key=lambda x: x[1][1], reverse=True)[:2] # Get only the bar/patch objects from the children bars = [child for child in ax.get_children() if hasattr(child, 'set_edgecolor')] for idx, _ in critical_tasks: if idx < len(bars): bars[idx].set_edgecolor('red') bars[idx].set_linewidth(2) ax.set_xlim(common_x_range) # Common x-axis range ax.set_xlabel('Months', fontsize=font_properties['axis_label_size'], fontname='Times New Roman') ax.set_title('Task Completion Progress', fontsize=font_properties['title_size'], fontname='Times New Roman') # Customize axis ticks ax.tick_params(axis='both', labelsize=font_properties['tick_size']) # Set the y-axis labels to task names ax.set_yticks(range(len(tasks))) ax.set_yticklabels([task[0] for task in tasks], fontsize=font_properties['tick_size'], fontname='Times New Roman') # Customize legend with selected position ax.legend(fontsize=font_properties['legend_size'], loc=legend_loc_map[legend_position]) # Add just before returning the figure in this function ax.legend(loc=legend_loc_map[legend_position], bbox_to_anchor=legend_bbox_map[legend_position]) plt.tight_layout() return fig # Function to create a timeline chart def plot_timeline_chart(tasks, bar_width, font_properties, progress_text_position, progress_text_size): fig, ax = plt.subplots(figsize=(10, 6)) colors = [current_theme[i % len(current_theme)] for i in range(len(tasks))] for i, (task_name, start, end, duration, completion) in enumerate(tasks): ax.barh(i, duration, left=start, color=colors[i], height=bar_width) # Add completion percentage based on selected position if progress_text_position == "on the bar": ax.text(start + duration/2, i, f"{completion}%", ha='center', va='center', fontsize=progress_text_size, fontname='Times New Roman', color='black' if completion < 50 else 'white') elif progress_text_position == "left side of the bar": ax.text(start - 0.5, i, f"{completion}%", ha='right', va='center', fontsize=progress_text_size, fontname='Times New Roman') elif progress_text_position == "right side of the bar": ax.text(end + 0.5, i, f"{completion}%", ha='left', va='center', fontsize=progress_text_size, fontname='Times New Roman') # Add milestones if enabled if show_milestones: for i, (task_name, start, end, duration, completion) in enumerate(tasks): if end % 3 == 0: # Just for demo - add milestones every 3 months if milestone_style == "star": ax.plot(end, i, marker="*", markersize=15, color=milestone_color) elif milestone_style == "diamond": ax.plot(end, i, marker="D", markersize=8, color=milestone_color) elif milestone_style == "triangle": ax.plot(end, i, marker="^", markersize=10, color=milestone_color) else: ax.plot(end, i, marker="o", markersize=8, color=milestone_color) # Add today line if enabled if show_today_line: today_month = 6 # For demo purposes ax.axvline(x=today_month, color='red', linestyle=today_line_style, linewidth=2, alpha=0.7, label='Today') ax.text(today_month, len(tasks)+0.2, 'Today', ha='center', color='red', fontsize=font_properties['tick_size']) ax.set_xlabel("Months", fontsize=font_properties['axis_label_size'], fontname='Times New Roman') ax.set_ylabel("Tasks", fontsize=font_properties['axis_label_size'], fontname='Times New Roman') ax.set_title("Project Timeline", fontsize=font_properties['title_size'], fontname='Times New Roman') ax.set_xticks(range(1, 13)) ax.set_xticklabels([datetime(2000, m, 1).strftime('%b') for m in range(1, 13)], fontsize=font_properties['tick_size'], fontname='Times New Roman') ax.set_yticks(range(len(tasks))) ax.set_yticklabels([t[0] for t in tasks], fontsize=font_properties['tick_size'], fontname='Times New Roman') # Add grid if enabled if show_grid: ax.grid(True, linestyle=grid_style, alpha=0.7) # Add legend with selected position if show_milestones or show_today_line: ax.legend(loc=legend_loc_map[legend_position], fontsize=font_properties['legend_size'], frameon=True) # Add just before returning the figure in this function ax.legend(loc=legend_loc_map[legend_position], bbox_to_anchor=legend_bbox_map[legend_position]) plt.tight_layout() return fig # Function to create a donut pie chart for overall project progress def create_dual_donut_chart(tasks, font_properties, border_line, show_inner_pie, show_outer_pie, pie_text_size, pie_text_orientation): # Check if 3D pie is requested if use_3d_pie: # Create a different kind of 3D visualization instead of a pie chart from mpl_toolkits.mplot3d import Axes3D fig = plt.figure(figsize=(8, 8)) ax = fig.add_subplot(111, projection='3d') # Calculate total and completed duration for the overall project total_duration = sum(task[1] for task in tasks) completed_duration = sum(task[1] * task[2] / 100 for task in tasks) # Create bar chart or another 3D visualization instead if show_inner_pie: # Create a simple 3D bar for overall progress values = [completed_duration, total_duration - completed_duration] labels = ['Completed', 'Remaining'] colors = [current_theme[0], adjust_color_brightness(current_theme[0], 0.5)] # Position the bars x = [0, 1] y = [0, 0] z = [0, 0] # Plot bars dx = dy = 0.5 dz = values ax.bar3d(x, y, z, dx, dy, dz, color=colors) # Add text using text2D which works with 3D axes for i, label in enumerate(labels): ax.text2D(0.3 + i*0.4, 0.5, f"{label}: {values[i]:.1f}", transform=ax.transAxes, fontsize=pie_text_size, fontname='Times New Roman') if show_outer_pie: # Create bars for individual tasks task_values = [task[1] * (task[2] / 100) for task in tasks] task_labels = [task[0] for task in tasks] colors = [current_theme[i % len(current_theme)] for i in range(len(tasks))] # Position the bars x = np.arange(len(tasks)) y = np.zeros_like(x) z = np.zeros_like(x) # Plot bars dx = dy = 0.5 dz = task_values ax.bar3d(x, y, z, dx, dy, dz, color=colors) # Add text labels for i, label in enumerate(task_labels): ax.text2D(0.1 + i*(0.8/len(tasks)), 0.2, f"{label}\n{task_values[i]:.1f}", transform=ax.transAxes, fontsize=pie_text_size-2, ha='center', fontname='Times New Roman') # Set title ax.set_title("Project Progress", fontsize=font_properties['title_size'], fontname='Times New Roman') # Set labels ax.set_xlabel('Tasks') ax.set_ylabel('') ax.set_zlabel('Progress') # Remove tick labels ax.set_xticklabels([]) ax.set_yticklabels([]) else: # Original 2D pie chart code fig, ax = plt.subplots(figsize=(8, 8)) # Calculate total and completed duration for the overall project total_duration = sum(task[1] for task in tasks) completed_duration = sum(task[1] * task[2] / 100 for task in tasks) # Prepare explode parameter for largest segment if enabled explode = None if explode_largest and show_outer_pie: task_sizes = [task[1] * (task[2] / 100) if task[1] != 0 else 0.1 for task in tasks] max_idx = task_sizes.index(max(task_sizes)) explode = [0.1 if i == max_idx else 0 for i in range(len(tasks))] # Inner donut: Overall project progress (if visible) if show_inner_pie: colors = [current_theme[0], adjust_color_brightness(current_theme[0], 0.5)] inner_wedges, inner_texts, inner_autotexts = ax.pie( [completed_duration, total_duration - completed_duration], labels=['Completed', 'Remaining'], colors=colors, autopct='%1.1f%%', startangle=90, wedgeprops={'width': 0.4, 'edgecolor': border_line, 'linewidth': 2.0}, shadow=False) # No shadow in 2D # Format inner pie text for text in inner_texts + inner_autotexts: text.set_fontsize(pie_text_size) text.set_fontname('Times New Roman') # Outer donut: Tasks performance (if visible) if show_outer_pie: task_sizes = [task[1] * (task[2] / 100) if task[1] != 0 else 0.1 for task in tasks] task_sizes = [max(size, 0.1) for size in task_sizes] task_labels = [task[0] for task in tasks] colors = [current_theme[i % len(current_theme)] for i in range(len(tasks))] outer_wedges, outer_texts, outer_autotexts = ax.pie( task_sizes, labels=task_labels, autopct='%1.1f%%', startangle=90, radius=0.6 - gap_size, colors=colors, wedgeprops={'width': 0.4, 'edgecolor': border_line, 'linewidth': 2.0}, explode=explode, shadow=False) # Format outer pie text and apply orientation for text in outer_texts + outer_autotexts: text.set_fontsize(pie_text_size) text.set_fontname('Times New Roman') # Apply text orientation if pie_text_orientation == "font print in vertical": text.set_rotation(90) elif pie_text_orientation == "font print in circular": if text in outer_texts: # Only rotate the labels, not the percentages angle = (text.get_position()[0] + text.get_position()[1]) * 180 text.set_rotation(angle) text.set_rotation_mode("anchor") text.set_va("center") ax.axis('equal') # Keep the pie chart circular ax.text(0, 0, "Project Progress", fontsize=font_properties['title_size'], fontname='Times New Roman', ha='center') # Add this right before the return fig statement ax.legend(loc=legend_loc_map[legend_position], bbox_to_anchor=legend_bbox_map[legend_position]) plt.tight_layout() return fig # Function to create an interactive gantt chart with plotly def create_gantt_chart(tasks): df = [] # Prepare data for gantt chart for i, (name, start, end, completion, depends_on, risk_level) in enumerate(tasks): # Set color based on risk level if risk_highlighting: if risk_level == "High": color = "rgb(255, 0, 0)" # Red for high risk elif risk_level == "Medium": color = "rgb(255, 165, 0)" # Orange for medium risk else: color = "rgb(0, 128, 0)" # Green for low risk else: color = current_theme[i % len(current_theme)] # Add main task bar df.append(dict( Task=name, Start=start, Finish=end, Complete=f"{completion}%", Resource=resource_tasks[i][1] if i < len(resource_tasks) else "", Priority=resource_tasks[i][3] if i < len(resource_tasks) else "Medium", Risk=risk_level, Color=color )) # Create figure fig = px.timeline( df, x_start="Start", x_end="Finish", y="Task", color="Risk" if risk_highlighting else None, color_discrete_map={ "High": "red", "Medium": "orange", "Low": "green" } if risk_highlighting else None, hover_data=["Complete", "Resource", "Priority"] ) # Add resource labels if enabled if show_resource_labels: for i, task in enumerate(df): fig.add_annotation( x=task["Start"] + (task["Finish"] - task["Start"])/2, y=task["Task"], text=task["Resource"], showarrow=False, font=dict(color="black", size=10) ) # Add dependencies if enabled if show_dependencies: for i, (name, start, end, completion, depends_on, risk) in enumerate(tasks): for dep in depends_on: # Find the dependent task index dep_idx = next((j for j, task in enumerate(tasks) if task[0] == dep), None) if dep_idx is not None: # Add an arrow from the end of dependent task to start of current task dep_end = tasks[dep_idx][2] fig.add_shape( type="line", x0=dep_end, y0=dep_idx, x1=start, y1=i, line=dict(color="grey", width=1, dash="dot"), layer="below" ) # Update layout fig.update_layout( title="Project Gantt Chart", height=gantt_height, xaxis_title="Timeline", yaxis_title="Tasks", font=dict(family="Times New Roman"), showlegend=True if risk_highlighting else False ) # Highlight weekends if needed if weekend_color: # This is simplified - in a real app, you'd calculate actual weekends fig.update_xaxes( rangebreaks=[dict(pattern="day of week", bounds=[6, 7])] ) return fig # Function to create resource allocation chart def create_resource_allocation_chart(tasks): if resource_chart_type == "Stacked Bar": # Extract resources and their allocations resources = {} for task in tasks: name, resource, allocation, priority, risk = task if resource not in resources: resources[resource] = [] resources[resource].append((name, allocation, priority, risk)) # Prepare data for plotting labels = list(resources.keys()) data = [] colors = [] for i, resource in enumerate(labels): resource_tasks = resources[resource] total_allocation = sum(allocation for _, allocation, _, _ in resource_tasks) data.append(total_allocation) # Fixed: Closed the parenthesis # Highlight overallocation if enabled if show_overallocation and total_allocation > overallocation_threshold: colors.append('red') else: colors.append(current_theme[i % len(current_theme)]) # Create figure fig = px.bar( x=labels, y=data, color=labels if not colors else None, color_discrete_sequence=colors if colors else current_theme, title="Resource Allocation", labels={'x': 'Resources', 'y': 'Allocation Percentage'}, text=[f"{d}%" for d in data] ) # Add overallocation threshold line if enabled if show_overallocation: fig.add_hline( y=overallocation_threshold, line_dash="dash", line_color="red", annotation_text=f"Overallocation Threshold ({overallocation_threshold}%)" ) # Group by priority or risk level if selected if group_by != "None": # Sort bars based on the selected grouping sorted_indices = [] if group_by == "Priority": # Define priority order (High > Medium > Low) priority_order = {"High": 0, "Medium": 1, "Low": 2} # Sort resources based on their highest priority task resource_priority = {} for resource in resources: tasks = resources[resource] # Get the highest priority (lowest value in priority_order) highest_priority = min([priority_order.get(t[2], 3) for t in tasks]) resource_priority[resource] = highest_priority # Sort resources by priority sorted_labels = sorted(labels, key=lambda r: resource_priority.get(r, 3)) elif group_by == "Risk Level": # Define risk order (High > Medium > Low) risk_order = {"High": 0, "Medium": 1, "Low": 2} # Sort resources based on their highest risk task resource_risk = {} for resource in resources: tasks = resources[resource] # Get the highest risk (lowest value in risk_order) highest_risk = min([risk_order.get(t[3], 3) for t in tasks]) resource_risk[resource] = highest_risk # Sort resources by risk sorted_labels = sorted(labels, key=lambda r: resource_risk.get(r, 3)) # Reorder the bars fig.update_layout(xaxis={'categoryorder': 'array', 'categoryarray': sorted_labels}) fig.update_layout( font=dict(family="Times New Roman"), xaxis_title="Resources", yaxis_title="Allocation (%)" ) return fig elif resource_chart_type == "Heatmap": # Create a matrix of task allocations by resource resources = set(task[1] for task in tasks) task_names = [task[0] for task in tasks] # Initialize allocation matrix allocation_matrix = [] for resource in resources: resource_allocations = [] for task_name in task_names: # Find allocation for this resource and task allocation = next((task[2] for task in tasks if task[0] == task_name and task[1] == resource), 0) resource_allocations.append(allocation) allocation_matrix.append(resource_allocations) # Create heatmap fig = go.Figure(data=go.Heatmap( z=allocation_matrix, x=task_names, y=list(resources), colorscale='Blues', text=[[f"{val}%" for val in row] for row in allocation_matrix], texttemplate="%{text}", textfont={"size": 10} )) fig.update_layout( title="Resource Allocation Heatmap", xaxis_title="Tasks", yaxis_title="Resources", font=dict(family="Times New Roman") ) return fig else: # Bubble Chart # Extract data for bubble chart x = [] # Task names y = [] # Resources size = [] # Allocation percentages colors = [] # Priority or risk colors for name, resource, allocation, priority, risk in tasks: x.append(name) y.append(resource) size.append(allocation) # Set color based on priority or risk if group_by == "Priority": if priority == "High": colors.append("red") elif priority == "Medium": colors.append("orange") else: colors.append("green") elif group_by == "Risk Level": if risk == "High": colors.append("red") elif risk == "Medium": colors.append("orange") else: colors.append("green") else: colors.append(current_theme[0]) # Create bubble chart fig = px.scatter( x=x, y=y, size=size, color=colors, title="Resource Allocation Bubble Chart", labels={"x": "Tasks", "y": "Resources", "size": "Allocation (%)"}, size_max=50, color_discrete_sequence=current_theme ) # Highlight overallocation if enabled if show_overallocation: # Calculate total allocation per resource resource_allocations = {} for name, resource, allocation, _, _ in tasks: if resource not in resource_allocations: resource_allocations[resource] = 0 resource_allocations[resource] += allocation # Add annotations for overallocated resources for resource, total in resource_allocations.items(): if total > overallocation_threshold: fig.add_annotation( x=x[-1], # Position at the last task y=resource, text=f"Overallocated ({total}%)", showarrow=True, arrowhead=1, ax=50, ay=0, font=dict(color="red") ) fig.update_layout( font=dict(family="Times New Roman"), showlegend=False ) return fig # Function to create executive dashboard def create_executive_dashboard(): # Create a multi-panel dashboard if dashboard_layout == "Grid": fig = make_subplots( rows=2, cols=2, specs=[[{"type": "indicator"}, {"type": "xy"}], [{"type": "xy"}, {"type": "xy"}]], subplot_titles=("Project Progress", "Task Completion", "Resource Allocation", "Burndown Chart") ) elif dashboard_layout == "Vertical": fig = make_subplots( rows=4, cols=1, specs=[[{"type": "indicator"}], [{"type": "xy"}], [{"type": "xy"}], [{"type": "xy"}]], subplot_titles=("Project Progress", "Task Completion", "Resource Allocation", "Burndown Chart") ) else: # Horizontal fig = make_subplots( rows=1, cols=4, specs=[[{"type": "indicator"}, {"type": "xy"}, {"type": "xy"}, {"type": "xy"}]], subplot_titles=("Project Progress", "Task Completion", "Resource Allocation", "Burndown Chart") ) # 1. KPI section # Calculate overall project completion total_work = sum(task[1] for task in tasks) completed_work = sum(task[1] * task[2] / 100 for task in tasks) overall_completion = (completed_work / total_work * 100) if total_work > 0 else 0 # Add progress gauge if dashboard_layout == "Grid": gauge_row, gauge_col = 1, 1 elif dashboard_layout == "Vertical": gauge_row, gauge_col = 1, 1 else: gauge_row, gauge_col = 1, 1 fig.add_trace( go.Indicator( mode="gauge+number", value=overall_completion, domain={'x': [0, 1], 'y': [0, 1]}, title={'text': "Overall Progress"}, gauge={ 'axis': {'range': [0, 100]}, 'bar': {'color': current_theme[0]}, 'steps': [ {'range': [0, 33], 'color': "lightgray"}, {'range': [33, 66], 'color': "gray"}, {'range': [66, 100], 'color': "darkgray"} ], 'threshold': { 'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 90 } } ), row=gauge_row, col=gauge_col ) # 2. Task completion section if dashboard_layout == "Grid": task_row, task_col = 1, 2 elif dashboard_layout == "Vertical": task_row, task_col = 2, 1 else: task_row, task_col = 1, 2 task_names = [task[0] for task in tasks] task_completion = [task[2] for task in tasks] fig.add_trace( go.Bar( x=task_names, y=task_completion, marker_color=[current_theme[i % len(current_theme)] for i in range(len(tasks))], text=[f"{c}%" for c in task_completion], textposition="auto" ), row=task_row, col=task_col ) # 3. Resource allocation section if dashboard_layout == "Grid": resource_row, resource_col = 2, 1 elif dashboard_layout == "Vertical": resource_row, resource_col = 3, 1 else: resource_row, resource_col = 1, 3 # Extract resources and their allocations resources = {} for task in resource_tasks: name, resource, allocation, priority, risk = task if resource not in resources: resources[resource] = 0 resources[resource] += allocation resource_names = list(resources.keys()) resource_allocations = list(resources.values()) fig.add_trace( go.Bar( x=resource_names, y=resource_allocations, marker_color=[current_theme[i % len(current_theme)] for i in range(len(resource_names))], text=[f"{a}%" for a in resource_allocations], textposition="auto" ), row=resource_row, col=resource_col ) # 4. Burndown chart if dashboard_layout == "Grid": burndown_row, burndown_col = 2, 2 elif dashboard_layout == "Vertical": burndown_row, burndown_col = 4, 1 else: burndown_row, burndown_col = 1, 4 # Generate burndown data if show_burndown: # Ideal burndown (linear) x = list(range(total_duration + 1)) if burndown_style == "Linear": y_ideal = [total_work - (total_work / total_duration) * i for i in x] elif burndown_style == "Custom": # Custom curve (faster at beginning) y_ideal = [total_work * (1 - (i / total_duration) ** 0.8) for i in x] else: # Ideal y_ideal = [total_work - (total_work / total_duration) * i for i in x] # Actual progress (simulated) actual_progress = [] remaining_work = total_work for i in range(total_duration + 1): if i == 0: actual_progress.append(remaining_work) else: # Simulate some variance in actual progress progress_rate = np.random.normal(total_work / total_duration, total_work / (total_duration * 4)) progress_rate = max(0, min(progress_rate, remaining_work)) # Bound the progress remaining_work -= progress_rate actual_progress.append(max(0, remaining_work)) # Adjust the last point to match overall completion actual_months_elapsed = 6 # For demo purposes actual_progress = actual_progress[:actual_months_elapsed + 1] fig.add_trace( go.Scatter( x=x, y=y_ideal, mode='lines', name='Ideal Burndown', line=dict(color='gray', dash='dash') ), row=burndown_row, col=burndown_col ) fig.add_trace( go.Scatter( x=list(range(len(actual_progress))), y=actual_progress, mode='lines+markers', name='Actual Progress', line=dict(color=current_theme[0]) ), row=burndown_row, col=burndown_col ) # Add word cloud if enabled if show_word_cloud: # This would typically be in a separate area or tab # For demo purposes, we create the word cloud data text = " ".join([f"{task[0]} " * int(task[1]) for task in tasks]) # Word cloud would be rendered separately, not in the Plotly subplot # Update layout for the entire dashboard fig.update_layout( title_text="Project Executive Dashboard", height=800 if dashboard_layout != "Horizontal" else 400, showlegend=True, legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 ), font=dict(family="Times New Roman") ) # Update axes labels fig.update_xaxes(title_text="Tasks", row=task_row, col=task_col) fig.update_yaxes(title_text="Completion %", row=task_row, col=task_col) fig.update_xaxes(title_text="Resources", row=resource_row, col=resource_col) fig.update_yaxes(title_text="Allocation %", row=resource_row, col=resource_col) fig.update_xaxes(title_text="Months", row=burndown_row, col=burndown_col) fig.update_yaxes(title_text="Remaining Work", row=burndown_row, col=burndown_col) return fig # Function to create a word cloud def create_word_cloud(): # Create text for word cloud text = " ".join([ f"{task[0]} " * int(task[1]) + f"{task[2]} " * 2 + f"{gantt_tasks[i][5]} " * 3 for i, task in enumerate(tasks) ]) # Add project name for emphasis text += f" {project_name} " * 10 # Generate color function based on selected theme def color_func(word, font_size, position, orientation, random_state=None, **kwargs): return np.random.choice(current_theme) # Create word cloud wordcloud = WordCloud( width=800, height=400, background_color='white', max_words=100, contour_width=3, contour_color='steelblue', colormap='viridis' # Change this to a named colormap ).generate(text) fig, ax = plt.subplots(figsize=(10, 5)) ax.imshow(wordcloud, interpolation='bilinear') ax.axis('off') ax.set_title(f"{project_name} Word Cloud", fontsize=font_properties['title_size'], fontname='Times New Roman') return fig # Export function def export_figure(fig, format='png'): if format.lower() == 'png' or format.lower() == 'pdf' or format.lower() == 'svg': buf = BytesIO() fig.savefig(buf, format=format.lower(), dpi=export_dpi, bbox_inches='tight') buf.seek(0) return buf elif format.lower() == 'html': # For Plotly figures if isinstance(fig, go.Figure): return fig.to_html(include_plotlyjs="cdn") else: # Convert matplotlib to plotly for HTML export plotly_fig = go.Figure() return plotly_fig.to_html(include_plotlyjs="cdn") else: raise ValueError(f"Unsupported format: {format}") # === Main Streamlit App Logic === # PROGRESS BAR CHART TAB with tab1: st.header("Task Progress Bar Chart") # Find the common x-axis range for proper scaling max_duration = max([task[1] for task in tasks]) if tasks else 0 common_x_range = (0, max_duration * 1.2) # Add some padding # Create progress bar chart progress_fig = create_dual_bar_chart( tasks, bar_width, task_colors, common_x_range, font_properties, completion_bar_width, progress_text_position, progress_text_size ) # Display the chart st.pyplot(progress_fig) # Add export button with st.expander("Export Chart"): export_col1, export_col2 = st.columns(2) with export_col1: if st.button("Export Progress Chart", key="export_progress"): buf = export_figure(progress_fig, export_format.lower()) # Generate download link based on format if export_format.lower() != 'html': st.download_button( label=f"Download {export_format}", data=buf, file_name=f"progress_chart.{export_format.lower()}", mime=f"image/{export_format.lower()}" ) else: st.download_button( label="Download HTML", data=buf, file_name="progress_chart.html", mime="text/html" ) with export_col2: st.write(f"Format: {export_format}, DPI: {export_dpi}") if include_header: st.write("Header/Footer will be included") if include_logo: st.write("Logo will be included") # TIMELINE CHART TAB with tab2: st.header("Project Timeline Chart") # Create timeline chart timeline_fig = plot_timeline_chart( timeline_tasks, timeline_bar_width, font_properties, progress_text_position, progress_text_size ) # Display the chart st.pyplot(timeline_fig) # Add export button with st.expander("Export Chart"): export_col1, export_col2 = st.columns(2) with export_col1: if st.button("Export Timeline Chart", key="export_timeline"): buf = export_figure(timeline_fig, export_format.lower()) # Generate download link based on format if export_format.lower() != 'html': st.download_button( label=f"Download {export_format}", data=buf, file_name=f"timeline_chart.{export_format.lower()}", mime=f"image/{export_format.lower()}" ) else: st.download_button( label="Download HTML", data=buf, file_name="timeline_chart.html", mime="text/html" ) with export_col2: st.write(f"Format: {export_format}, DPI: {export_dpi}") if include_header: st.write("Header/Footer will be included") if include_logo: st.write("Logo will be included") # PIE CHART TAB with tab3: st.header("Project Progress Pie Chart") # Create donut pie chart pie_fig = create_dual_donut_chart( tasks, font_properties, border_line, show_inner_pie, show_outer_pie, pie_text_size, pie_text_orientation ) # Display the chart st.pyplot(pie_fig) # Add export button with st.expander("Export Chart"): export_col1, export_col2 = st.columns(2) with export_col1: if st.button("Export Pie Chart", key="export_pie"): buf = export_figure(pie_fig, export_format.lower()) # Generate download link based on format if export_format.lower() != 'html': st.download_button( label=f"Download {export_format}", data=buf, file_name=f"pie_chart.{export_format.lower()}", mime=f"image/{export_format.lower()}" ) else: st.download_button( label="Download HTML", data=buf, file_name="pie_chart.html", mime="text/html" ) with export_col2: st.write(f"Format: {export_format}, DPI: {export_dpi}") if include_header: st.write("Header/Footer will be included") if include_logo: st.write("Logo will be included") # GANTT CHART TAB with tab4: st.header("Project Gantt Chart") # Create Gantt chart gantt_fig = create_gantt_chart(gantt_tasks) # Display the chart st.plotly_chart(gantt_fig, use_container_width=True) # Add export button with st.expander("Export Chart"): export_col1, export_col2 = st.columns(2) with export_col1: if st.button("Export Gantt Chart", key="export_gantt"): if export_format.lower() == 'html': html_gantt = export_figure(gantt_fig, 'html') st.download_button( label="Download HTML", data=html_gantt, file_name="gantt_chart.html", mime="text/html" ) else: st.error(f"Gantt chart can only be exported as HTML currently") with export_col2: st.write(f"Format: {export_format}") if include_header: st.write("Header/Footer will be included") if include_logo: st.write("Logo will be included") # RESOURCE ALLOCATION TAB with tab5: st.header("Resource Allocation Dashboard") # Create resource allocation chart resource_fig = create_resource_allocation_chart(resource_tasks) # Display the chart st.plotly_chart(resource_fig, use_container_width=True) # Add export button with st.expander("Export Chart"): export_col1, export_col2 = st.columns(2) with export_col1: if st.button("Export Resource Chart", key="export_resource"): if export_format.lower() == 'html': html_resource = export_figure(resource_fig, 'html') st.download_button( label="Download HTML", data=html_resource, file_name="resource_chart.html", mime="text/html" ) else: st.error(f"Resource chart can only be exported as HTML currently") with export_col2: st.write(f"Format: {export_format}") if include_header: st.write("Header/Footer will be included") if include_logo: st.write("Logo will be included") # EXECUTIVE DASHBOARD TAB with tab6: st.header("Executive Dashboard") # Create dashboard dashboard_fig = create_executive_dashboard() # Display the dashboard st.plotly_chart(dashboard_fig, use_container_width=True) # Display word cloud if enabled if show_word_cloud: st.subheader("Project Word Cloud") wordcloud_fig = create_word_cloud() # Using the fixed function st.pyplot(wordcloud_fig) # Add export button with st.expander("Export Dashboard"): export_col1, export_col2 = st.columns(2) with export_col1: if st.button("Export Dashboard", key="export_dashboard"): if export_format.lower() == 'html': html_dashboard = export_figure(dashboard_fig, 'html') st.download_button( label="Download HTML", data=html_dashboard, file_name="executive_dashboard.html", mime="text/html" ) else: st.error(f"Dashboard can only be exported as HTML currently") with export_col2: st.write(f"Format: {export_format}") if include_header: st.write("Header/Footer will be included") if include_logo: st.write("Logo will be included") # Footer st.markdown("---") st.markdown("© Project Timeline Management App 2025")