Initial commit
This commit is contained in:
99
ui/stats.py
Normal file
99
ui/stats.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import customtkinter as ctk
|
||||
from database import Database
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
import datetime
|
||||
|
||||
class StatsFrame(ctk.CTkFrame):
|
||||
def __init__(self, master, db: Database):
|
||||
super().__init__(master)
|
||||
self.db = db
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.header = ctk.CTkLabel(self, text="Progress & Motivation", font=ctk.CTkFont(size=20, weight="bold"))
|
||||
self.header.grid(row=0, column=0, pady=10, sticky="w")
|
||||
|
||||
# Stats Summary (Streaks, etc.)
|
||||
self.summary_frame = ctk.CTkFrame(self)
|
||||
self.summary_frame.grid(row=1, column=0, sticky="ew", pady=10)
|
||||
|
||||
logs = self.db.get_log_history(30)
|
||||
streak = self.calculate_streak(logs)
|
||||
|
||||
self.streak_label = ctk.CTkLabel(self.summary_frame, text=f"🔥 Current Streak: {streak} Days", font=ctk.CTkFont(size=16))
|
||||
self.streak_label.pack(side="left", padx=20, pady=10)
|
||||
|
||||
# Chart Section
|
||||
self.chart_frame = ctk.CTkFrame(self)
|
||||
self.chart_frame.grid(row=2, column=0, sticky="nsew", pady=10)
|
||||
self.chart_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.plot_weight_waist(logs)
|
||||
|
||||
def calculate_streak(self, logs):
|
||||
if not logs: return 0
|
||||
streak = 0
|
||||
today = datetime.date.today()
|
||||
|
||||
# Simple streak calculation based on existing logs
|
||||
# Checks if logs are consecutive
|
||||
log_dates = [datetime.datetime.strptime(l['date'], "%Y-%m-%d").date() for l in logs]
|
||||
log_dates.sort(reverse=True)
|
||||
|
||||
current = today
|
||||
if log_dates and log_dates[0] < today - datetime.timedelta(days=1):
|
||||
return 0 # Missed yesterday and today
|
||||
|
||||
for d in log_dates:
|
||||
if d == current or d == current - datetime.timedelta(days=1):
|
||||
streak += 1
|
||||
current = d
|
||||
else:
|
||||
break
|
||||
return streak
|
||||
|
||||
def plot_weight_waist(self, logs):
|
||||
if not logs or len(logs) < 2:
|
||||
ctk.CTkLabel(self.chart_frame, text="Log data for at least 2 days to see charts.").pack(pady=50)
|
||||
return
|
||||
|
||||
# Prepare data
|
||||
logs.reverse() # Chronological
|
||||
dates = [l['date'][5:] for l in logs if l['weight'] or l['waist']] # MM-DD
|
||||
weights = [l['weight'] for l in logs if l['weight'] or l['waist']]
|
||||
waists = [l['waist'] for l in logs if l['weight'] or l['waist']]
|
||||
|
||||
if not weights: return
|
||||
|
||||
# Create plot
|
||||
fig, ax1 = plt.subplots(figsize=(6, 4), dpi=100)
|
||||
fig.patch.set_facecolor('#2b2b2b') # Matches dark mode
|
||||
ax1.set_facecolor('#2b2b2b')
|
||||
|
||||
color = 'tab:blue'
|
||||
ax1.set_xlabel('Date')
|
||||
ax1.set_ylabel('Weight (kg)', color=color)
|
||||
ax1.plot(dates, weights, color=color, marker='o', label='Weight')
|
||||
ax1.tick_params(axis='y', labelcolor=color)
|
||||
ax1.tick_params(axis='x', rotation=45, colors='white')
|
||||
ax1.spines['bottom'].set_color('white')
|
||||
ax1.spines['top'].set_color('none')
|
||||
ax1.spines['left'].set_color('white')
|
||||
ax1.spines['right'].set_color('none')
|
||||
|
||||
# Second axis for Waist
|
||||
ax2 = ax1.twinx()
|
||||
color = 'tab:red'
|
||||
ax2.set_ylabel('Waist (cm)', color=color)
|
||||
ax2.plot(dates, waists, color=color, marker='x', linestyle='--', label='Waist')
|
||||
ax2.tick_params(axis='y', labelcolor=color)
|
||||
ax2.spines['right'].set_color('white')
|
||||
|
||||
fig.tight_layout()
|
||||
|
||||
canvas = FigureCanvasTkAgg(fig, master=self.chart_frame)
|
||||
canvas.draw()
|
||||
canvas.get_tk_widget().pack(fill="both", expand=True)
|
||||
Reference in New Issue
Block a user