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 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] # 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)", 1, total_duration, min(3, total_duration), key=f"expected_duration_{i}") completed_percentage = st.slider(f"Completion Percentage", 0, 100, 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 midle", "top left", "mid right", "mid midle", "mid left", "bottom right", "bottom midle", "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 midle": "upper center", "top left": "upper left", "mid right": "center right", "mid midle": "center", "mid left": "center left", "bottom right": "lower right", "bottom midle": "lower center", "bottom left": "lower left" } # 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") # Helper Functions 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}" # 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] for idx, _ in critical_tasks: ax.get_children()[idx].set_edgecolor('red') ax.get_children()[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]) 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) plt.tight_layout() return fig # Function to create a donut pie chart for overall project progress def create_dual_donut_chart(total_duration, completed_duration, tasks, gap_size, font_properties, border_line, show_inner_pie, show_outer_pie, pie_text_size, pie_text_orientation): fig, ax = plt.subplots(figsize=(8, 8)) # 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))] # Configure 3D effect if enabled if use_3d_pie: ax = plt.subplot(111, projection='3d') fig.subplots_adjust(left=0, right=1, bottom=0, top=1) # 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=use_3d_pie) # 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] # Avoid zero sizes 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=use_3d_pie) # 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") if not use_3d_pie: 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') 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) # Color based on overallocation if show_overallocation and total_allocation > overallocation_threshold: colors.append("red") else: colors.append(current_theme[i % len(current_theme)]) # Create figure fig, ax = plt.subplots(figsize=(10, 6)) ax.bar(labels, data, color=colors) # Add threshold line if overallocation checking is enabled if show_overallocation: ax.axhline(y=overallocation_threshold, color='red', linestyle='--', label=f"Threshold ({overallocation_threshold}%)") # Highlight overallocated resources if show_overallocation: for i, allocation in enumerate(data): if allocation > overallocation_threshold: ax.annotate("Overallocated", xy=(i, allocation), xytext=(0, 10), textcoords="offset points", ha='center',