Spaces:
Sleeping
Sleeping
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 | |
} | |
# Set the legend with both loc and bbox_to_anchor to avoid overlap | |
ax.legend(loc=legend_loc_map[legend_position], bbox_to_anchor=legend_bbox_map[legend_position]) | |
# 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") |