MFLF-Demo / src /chatbot /dashboard /visualize_component.py
Focussy's picture
change/translate-to-thai
42407ae
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
def generate_budget_utilization_gauge_chart(total_budget, total_expense):
"""Generate a gauge chart comparing budget vs. total expense."""
gauge_steps = [
{'range': [0, total_budget * 0.7], 'color': '#d4f1dd'},
{'range': [total_budget * 0.7, total_budget * 0.9], 'color': '#fff2cc'},
{'range': [total_budget * 0.9, total_budget * 1.2], 'color': '#f8d7da'}
]
gauge_threshold = {
'line': {'color': 'red', 'width': 4},
'thickness': 0.75,
'value': total_budget
}
gauge = go.Indicator(
mode="gauge+number+delta",
value=total_expense,
title={'text': "ค่าใช้จ่ายทั้งหมด", 'font': {'size': 24}},
delta={
'reference': total_budget,
'increasing': {'color': 'red', 'symbol': "\u25B2"},
'decreasing': {'color': 'green', 'symbol': "\u25BC"}
},
gauge={
'axis': {'range': [None, total_budget * 1.2], 'tickwidth': 1},
'bar': {'color': '#1f77b4'},
'bgcolor': 'white',
'borderwidth': 2,
'bordercolor': 'gray',
'steps': gauge_steps,
'threshold': gauge_threshold
}
)
fig = go.Figure(gauge)
fig.update_layout(
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)',
margin=dict(t=50, r=25, l=25, b=25),
height=300
)
return fig
def generate_deliverable_budget_vs_expense_bar_chart(budget_df, expense_df):
"""Generate a grouped bar chart comparing budget vs. actual expense per deliverable."""
# Calculate total budget per deliverable
budget_df['total_budget'] = budget_df[['wage', 'materials', 'tools_equipment', 'services', 'misc']].sum(axis=1)
budget_df['title'] = budget_df.get('title', pd.Series(["Unnamed Deliverable"] * len(budget_df)))
# Prepare budget summary
budget_summary = budget_df[['title', 'total_budget']].rename(columns={'title': 'deliverable_title'})
# Summarize expenses per deliverable
expense_summary = (
expense_df.groupby('associated_deliverable_id')['total_payment_amount']
.sum()
.reset_index()
)
# Map deliverable titles to expenses
deliverable_titles = budget_df[['deliverable_id', 'title']].drop_duplicates()
expense_summary = (
pd.merge(
expense_summary,
deliverable_titles,
left_on='associated_deliverable_id',
right_on='deliverable_id',
how='left'
)
.rename(columns={
'title': 'deliverable_title',
'total_payment_amount': 'spending'
})
)
# Merge budget and expense summaries
merged_df = pd.merge(
budget_summary,
expense_summary[['deliverable_title', 'spending']],
on='deliverable_title',
how='left'
).fillna({'spending': 0})
# Create bar chart
fig = go.Figure()
fig.add_trace(go.Bar(
x=merged_df['deliverable_title'],
y=merged_df['total_budget'],
name='งบประมาณ',
marker_color='green'
))
fig.add_trace(go.Bar(
x=merged_df['deliverable_title'],
y=merged_df['spending'],
name='ค่าใช้จ่าย',
marker_color='red'
))
fig.update_layout(
title='แผนภูมิเปรียบเทียบงบประมาณและค่าใช้จ่าย',
barmode='group',
xaxis_title='การส่งมอบ',
yaxis_title='จำนวนเงิน (บาท)',
legend=dict(orientation='h', y=-0.2),
height=400,
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)',
margin=dict(t=50, r=25, l=25, b=25)
)
return fig
def generate_spending_distribution_pie_chart(expense_df, budget_df):
"""Generate a pie chart showing the distribution of spending by deliverables."""
# Summarize total spending per deliverable
spending_summary = (
expense_df.groupby('associated_deliverable_id')['total_payment_amount']
.sum()
.reset_index()
)
# Map deliverable titles
deliverable_titles = budget_df[['deliverable_id', 'title']].drop_duplicates()
spending_summary = (
pd.merge(
spending_summary,
deliverable_titles,
left_on='associated_deliverable_id',
right_on='deliverable_id',
how='left'
)
.rename(columns={
'title': 'deliverable_title',
'total_payment_amount': 'amount'
})
)
# Handle missing titles
spending_summary['deliverable_title'] = spending_summary['deliverable_title'].fillna("Unnamed Deliverable")
# Create pie chart
fig = px.pie(
spending_summary,
values='amount',
names='deliverable_title',
title='ยอดค่าใช้จ่ายของการส่งมอบ',
color_discrete_sequence=px.colors.qualitative.Set2
)
fig.update_traces(
textinfo='label+value',
hoverinfo='label+percent',
textposition='inside',
insidetextorientation='radial'
)
fig.update_layout(
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)',
margin=dict(t=50, r=25, l=25, b=25),
height=400
)
return fig
def generate_daily_spending_bar_chart(expense_df):
"""Generate a bar chart showing daily spending amounts."""
# Prepare date and group by date
expense_df['transaction_date'] = pd.to_datetime(expense_df['transaction_date']).dt.date
daily_summary = (
expense_df.groupby('transaction_date')['total_payment_amount']
.sum()
.reset_index()
.rename(columns={
'transaction_date': 'date',
'total_payment_amount': 'amount'
})
.sort_values('date')
)
# Create bar chart
fig = go.Figure()
fig.add_trace(go.Bar(
x=daily_summary['date'],
y=daily_summary['amount'],
name='ค่าใช้จ่ายในแต่ละวัน',
marker_color='#1f77b4'
))
fig.update_layout(
title='ค่าใช้จ่ายในแต่ละวัน',
xaxis_title='วันที่',
yaxis_title='จำนวนเงิน (บาท)',
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)',
margin=dict(t=50, r=25, l=25, b=25),
height=400
)
return fig
def generate_cumulative_spending_line_chart(expense_df):
"""Generate a line chart showing cumulative spending over time."""
# Convert to date and summarize spending per day
expense_df['transaction_date'] = pd.to_datetime(expense_df['transaction_date']).dt.date
daily_summary = (
expense_df.groupby('transaction_date')['total_payment_amount']
.sum()
.reset_index()
.rename(columns={'transaction_date': 'date', 'total_payment_amount': 'amount'})
.sort_values('date')
)
# Calculate cumulative spending
daily_summary['cumulative_amount'] = daily_summary['amount'].cumsum()
# Create line chart
fig = go.Figure()
fig.add_trace(go.Scatter(
x=daily_summary['date'],
y=daily_summary['cumulative_amount'],
mode='lines+markers',
name='Cumulative Spending',
marker_color='#ff7f0e'
))
fig.update_layout(
title='ยอดค่าใช้จ่ายสะสมตามช่วงเวลา',
xaxis_title='วันที่',
yaxis_title='ยอดค่าใช้จ่ายสะสม (บาท)',
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)',
margin=dict(t=50, r=25, l=25, b=25),
height=400
)
return fig
def generate_risk_level_distribution_pie_chart(deliverable_df):
"""Generate a pie chart showing the distribution of deliverables by risk level."""
risk_summary = (
deliverable_df['risk_level']
.value_counts()
.reset_index(name='count')
.rename(columns={'index': 'risk_level'})
)
risk_color_map = {
'green': '#28a745',
'yellow': '#ffc107',
'red': '#dc3545',
'unknown': '#6c757d'
}
fig = px.pie(
risk_summary,
values='count',
names='risk_level',
title='แผนภูมิระดับความเสี่ยง',
color='risk_level',
color_discrete_map=risk_color_map
)
fig.update_traces(
textinfo='label+percent',
hoverinfo='label+value+percent',
textposition='inside'
)
fig.update_layout(
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)',
margin=dict(t=50, r=25, l=25, b=25),
height=400
)
return fig
def generate_deliverable_timeline_gantt_chart(deliverable_df):
"""Generate a Gantt chart showing deliverables over time, colored by risk level."""
df = deliverable_df.copy()
# Ensure proper date format
df['start_date'] = pd.to_datetime(df['start_date'])
df['end_date'] = pd.to_datetime(df['end_date'])
# Fill missing values
df['title'] = df['title'].fillna("Unnamed Deliverable")
df['risk_level'] = df['risk_level'].fillna("unknown") # lowercase to match color map
# Rename columns for display
df = df.rename(columns={
'title': 'หัวข้อ',
'risk_level': 'ระดับความเสี่ยง'
})
risk_color_map = {
'green': '#28a745',
'yellow': '#ffc107',
'red': '#dc3545',
'unknown': '#6c757d'
}
# Create Gantt chart
fig = px.timeline(
df,
x_start='start_date',
x_end='end_date',
y='หัวข้อ',
color='ระดับความเสี่ยง',
title='ไทม์ไลน์การส่งมอบ (Gantt Chart)',
hover_data=['status', 'risk_level_rationale'],
color_discrete_map=risk_color_map
)
# Reverse Y-axis to have earliest on top
fig.update_yaxes(autorange='reversed')
# Layout customization
fig.update_layout(
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)',
margin=dict(t=50, r=25, l=25, b=25),
height=500
)
return fig
def render_deliverable_summary_cards(deliverable_df):
"""Render HTML cards for deliverables with expandable risk rationale using <details>/<summary> (JS-free, Gradio-safe)."""
def format_date(date_str):
if pd.isna(date_str):
return "-"
return pd.to_datetime(date_str).strftime("%d %b %Y")
risk_level_colors = {
"green": "#28a745",
"yellow": "#ffc107",
"red": "#dc3545",
"unknown": "#6c757d"
}
status_colors = {
"ongoing": "#17a2b8",
"done": "#007bff",
}
cards_html = """
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
"""
for _, row in deliverable_df.iterrows():
title = row.get('title', 'Untitled Deliverable')
deliverable_id = row.get('deliverable_id', '-')
status = str(row.get('status', 'N/A')).lower()
risk_level = str(row.get('risk_level', 'unknown')).lower()
rationale = row.get('risk_level_rationale', 'No rationale provided')
status_color = status_colors.get(status, "#6c757d")
risk_color = risk_level_colors.get(risk_level, "#6c757d")
status_map = {
'done': 'เสร็จสิ้น',
'ongoing': 'กำลังดำเนินการ'
}
thai_status = status_map.get(status, 'ไม่ทราบสถานะ')
start_date = format_date(row.get('start_date'))
end_date = format_date(row.get('end_date'))
cards_html += f"""
<div style='border: 1px solid #dee2e6; border-radius: 10px; padding: 15px; background-color: #ffffff; box-shadow: 0 2px 6px rgba(0,0,0,0.05);'>
<div style='font-weight: bold; font-size: 1.1em; margin-bottom: 5px;'>
{title}
<div style='font-size: 0.85em; color: #6c757d;'>ID: {deliverable_id}</div>
</div>
<div style='margin: 8px 0;'>
<span style='padding: 3px 8px; border-radius: 4px; font-size: 0.75em; background-color: {status_color}; color: white; margin-right: 5px;'>
{thai_status}
</span>
<span style='padding: 3px 8px; border-radius: 4px; font-size: 0.75em; background-color: {risk_color}; color: white;'>
ระดับความเสี่ยง: {risk_level.capitalize()}
</span>
</div>
<div style='font-size: 0.9em; color: #6c757d;'>
<strong>เริ่มต้น:</strong> {start_date}<br>
<strong>สิ้นสุด:</strong> {end_date}
</div>
<details style='margin-top: 12px; font-size: 0.85em; color: #495057;'>
<summary style='cursor: pointer; color: #007bff;'>แสดงเหตุผลของความเสี่ยง</summary>
<div style='margin-top: 6px; padding: 8px; background-color: #f8f9fa; border-radius: 5px; border: 1px solid #dee2e6;'>
<strong>เหตุผลของความเสี่ยง:</strong><br>{rationale}
</div>
</details>
</div>
"""
cards_html += "</div>"
return cards_html