From 1f1dab8d8aa1960219af63e9400c075a8c90b0a2 Mon Sep 17 00:00:00 2001 From: Shubham Kumar Date: Thu, 19 Jun 2025 10:11:32 +0530 Subject: [PATCH 1/2] Add: Habit Tracker project --- habit_tracker/README.md | 39 +++ .../__pycache__/habit_tracker.cpython-313.pyc | Bin 0 -> 17577 bytes .../habit_tracker_terminal.cpython-313.pyc | Bin 0 -> 17586 bytes habit_tracker/habit_tracker_gui.py | 235 +++++++++++++++ habit_tracker/habit_tracker_terminal.py | 274 ++++++++++++++++++ habit_tracker/requirements.txt | 3 + 6 files changed, 551 insertions(+) create mode 100644 habit_tracker/README.md create mode 100644 habit_tracker/__pycache__/habit_tracker.cpython-313.pyc create mode 100644 habit_tracker/__pycache__/habit_tracker_terminal.cpython-313.pyc create mode 100644 habit_tracker/habit_tracker_gui.py create mode 100644 habit_tracker/habit_tracker_terminal.py create mode 100644 habit_tracker/requirements.txt diff --git a/habit_tracker/README.md b/habit_tracker/README.md new file mode 100644 index 00000000..8e4963da --- /dev/null +++ b/habit_tracker/README.md @@ -0,0 +1,39 @@ +![Star Badge](https://img.shields.io/static/v1?label=%F0%9F%8C%9F&message=If%20Useful&style=style=flat&color=BC4E99) +![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=103) + +# Habit Tracker + +This open-source project is a simple Habit Tracker built with Python. It's designed for beginners to learn and practice Python by tracking daily habits. You can freely modify the code, fix bugs, or add new features. Contributions are welcome! You can: +1. Improve functionality +2. Fix bugs +3. Add new features + +Every contribution, big or small, is appreciated. + +## ⚙️ Languages or Frameworks Used + +The project uses Python. All required modules are listed in the `requirements.txt` file. To install dependencies, run the command provided in the file using your terminal. + +## 🌟 How to run + +To run the script, use: +``` +python +``` +or, if that doesn't work: +``` +python3 +``` +To stop the script, press `CTRL + C`. + +## 📺 Demo + +Below is a demo of the Habit Tracker in action: + + + +## 🤖 Author + +This script is by shubham kumar. + +[shubham kumar GitHub Profile](https://github.com/shubham-kumr) \ No newline at end of file diff --git a/habit_tracker/__pycache__/habit_tracker.cpython-313.pyc b/habit_tracker/__pycache__/habit_tracker.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00bb85f4ea3dc6dabf13a450f3708004c62e0993 GIT binary patch literal 17577 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 literal 0 HcmV?d00001 diff --git a/habit_tracker/habit_tracker_gui.py b/habit_tracker/habit_tracker_gui.py new file mode 100644 index 00000000..7bcd5825 --- /dev/null +++ b/habit_tracker/habit_tracker_gui.py @@ -0,0 +1,235 @@ +import tkinter as tk +from tkinter import ttk, messagebox +from ttkthemes import ThemedTk +import json +import datetime +from habit_tracker_terminal import HabitTracker, Habit +import matplotlib.pyplot as plt +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from PIL import Image, ImageTk + +class HabitTrackerGUI: + def __init__(self): + self.tracker = HabitTracker() + self.tracker.load_from_file() + + self.root = ThemedTk(theme="arc") # Modern theme + self.root.title("Habit Tracker") + self.root.geometry("1200x800") + + # Configure grid + self.root.grid_columnconfigure(0, weight=1) + self.root.grid_columnconfigure(1, weight=3) + self.root.grid_rowconfigure(0, weight=1) + + # Create main containers + self.create_sidebar() + self.create_main_content() + + # Style configuration + self.style = ttk.Style() + self.style.configure("Custom.TFrame", background="#f0f0f0") + self.style.configure("Sidebar.TButton", padding=10, width=20) + + def create_sidebar(self): + sidebar = ttk.Frame(self.root, style="Custom.TFrame", padding="10") + sidebar.grid(row=0, column=0, sticky="nsew") + + # Buttons + ttk.Button(sidebar, text="Add Habit", command=self.show_add_habit_dialog, + style="Sidebar.TButton").pack(pady=5) + ttk.Button(sidebar, text="View All Habits", command=self.show_all_habits, + style="Sidebar.TButton").pack(pady=5) + ttk.Button(sidebar, text="Categories", command=self.show_categories, + style="Sidebar.TButton").pack(pady=5) + ttk.Button(sidebar, text="Statistics", command=self.show_statistics, + style="Sidebar.TButton").pack(pady=5) + ttk.Button(sidebar, text="Save", command=self.save_habits, + style="Sidebar.TButton").pack(pady=5) + + def create_main_content(self): + self.main_content = ttk.Frame(self.root, padding="20") + self.main_content.grid(row=0, column=1, sticky="nsew") + self.show_all_habits() # Show habits by default + + def show_add_habit_dialog(self): + dialog = tk.Toplevel(self.root) + dialog.title("Add New Habit") + dialog.geometry("400x500") + dialog.transient(self.root) + + ttk.Label(dialog, text="Habit Name:").pack(pady=5) + name_entry = ttk.Entry(dialog, width=30) + name_entry.pack(pady=5) + + ttk.Label(dialog, text="Category:").pack(pady=5) + category_entry = ttk.Entry(dialog, width=30) + category_entry.pack(pady=5) + + ttk.Label(dialog, text="Goal Frequency:").pack(pady=5) + frequency_var = tk.StringVar(value="daily") + frequencies = ["daily", "weekly", "monthly"] + frequency_combo = ttk.Combobox(dialog, textvariable=frequency_var, + values=frequencies, state="readonly", width=27) + frequency_combo.pack(pady=5) + + ttk.Label(dialog, text="Goal Count:").pack(pady=5) + count_var = tk.StringVar(value="1") + count_entry = ttk.Entry(dialog, textvariable=count_var, width=30) + count_entry.pack(pady=5) + + def save_habit(): + name = name_entry.get().strip() + if name: + self.tracker.add_habit( + name, + category_entry.get().strip() or None, + frequency_var.get(), + int(count_var.get()) + ) + dialog.destroy() + self.show_all_habits() + else: + messagebox.showerror("Error", "Habit name is required!") + + ttk.Button(dialog, text="Save", command=save_habit).pack(pady=20) + + def show_all_habits(self): + for widget in self.main_content.winfo_children(): + widget.destroy() + + # Create a canvas for scrolling + canvas = tk.Canvas(self.main_content) + scrollbar = ttk.Scrollbar(self.main_content, orient="vertical", command=canvas.yview) + scrollable_frame = ttk.Frame(canvas) + + scrollable_frame.bind( + "", + lambda e: canvas.configure(scrollregion=canvas.bbox("all")) + ) + + canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") + canvas.configure(yscrollcommand=scrollbar.set) + + # Headers + headers = ["Habit", "Category", "Streak", "Today's Progress", "Actions"] + for col, header in enumerate(headers): + ttk.Label(scrollable_frame, text=header, font=("TkDefaultFont", 10, "bold")).grid( + row=0, column=col, padx=10, pady=5, sticky="w") + + # Habit rows + for idx, (name, habit) in enumerate(self.tracker.habits.items(), 1): + ttk.Label(scrollable_frame, text=name).grid( + row=idx, column=0, padx=10, pady=5, sticky="w") + ttk.Label(scrollable_frame, text=habit.category or "Uncategorized").grid( + row=idx, column=1, padx=10, pady=5, sticky="w") + ttk.Label(scrollable_frame, text=f"{habit.get_streak()} days").grid( + row=idx, column=2, padx=10, pady=5, sticky="w") + + progress = habit.get_goal_progress() + progress_bar = ttk.Progressbar(scrollable_frame, length=100, mode='determinate') + progress_bar['value'] = progress + progress_bar.grid(row=idx, column=3, padx=10, pady=5) + + actions_frame = ttk.Frame(scrollable_frame) + actions_frame.grid(row=idx, column=4, padx=10, pady=5) + + ttk.Button(actions_frame, text="✓", width=3, + command=lambda n=name: self.mark_completed(n)).pack(side=tk.LEFT, padx=2) + ttk.Button(actions_frame, text="📊", width=3, + command=lambda n=name: self.show_habit_stats(n)).pack(side=tk.LEFT, padx=2) + ttk.Button(actions_frame, text="🗑", width=3, + command=lambda n=name: self.delete_habit(n)).pack(side=tk.LEFT, padx=2) + + canvas.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + def show_categories(self): + for widget in self.main_content.winfo_children(): + widget.destroy() + + categories = self.tracker.get_habits_by_category() + + for category, habits in categories.items(): + frame = ttk.LabelFrame(self.main_content, text=category, padding="10") + frame.pack(fill="x", pady=5) + + for habit in habits: + habit_frame = ttk.Frame(frame) + habit_frame.pack(fill="x", pady=2) + + ttk.Label(habit_frame, text=habit).pack(side=tk.LEFT) + progress = self.tracker.habits[habit].get_goal_progress() + progress_bar = ttk.Progressbar(habit_frame, length=200, mode='determinate') + progress_bar['value'] = progress + progress_bar.pack(side=tk.LEFT, padx=10) + + def show_statistics(self): + for widget in self.main_content.winfo_children(): + widget.destroy() + + fig = plt.figure(figsize=(10, 6)) + ax = fig.add_subplot(111) + + habits = list(self.tracker.habits.keys()) + completion_rates = [self.tracker.get_completion_stats(habit, 'week') + for habit in habits] + + ax.bar(habits, completion_rates) + ax.set_title('Weekly Completion Rates') + ax.set_ylabel('Completion Rate (%)') + plt.xticks(rotation=45) + + canvas = FigureCanvasTkAgg(fig, master=self.main_content) + canvas.draw() + canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) + + def mark_completed(self, habit_name): + self.tracker.mark_completed(habit_name) + self.show_all_habits() + + def delete_habit(self, habit_name): + if messagebox.askyesno("Confirm Delete", + f"Are you sure you want to delete {habit_name}?"): + self.tracker.delete_habit(habit_name) + self.show_all_habits() + + def show_habit_stats(self, habit_name): + dialog = tk.Toplevel(self.root) + dialog.title(f"Statistics - {habit_name}") + dialog.geometry("600x400") + dialog.transient(self.root) + + notebook = ttk.Notebook(dialog) + notebook.pack(fill="both", expand=True, padx=10, pady=10) + + # Timeline tab + timeline_frame = ttk.Frame(notebook) + notebook.add(timeline_frame, text="Timeline") + + fig1 = plt.figure(figsize=(8, 4)) + self.tracker.visualize_habit(habit_name, "timeline") + canvas1 = FigureCanvasTkAgg(fig1, master=timeline_frame) + canvas1.draw() + canvas1.get_tk_widget().pack(fill=tk.BOTH, expand=True) + + # Heatmap tab + heatmap_frame = ttk.Frame(notebook) + notebook.add(heatmap_frame, text="Heatmap") + + fig2 = plt.figure(figsize=(8, 4)) + self.tracker.visualize_habit(habit_name, "heatmap") + canvas2 = FigureCanvasTkAgg(fig2, master=heatmap_frame) + canvas2.draw() + canvas2.get_tk_widget().pack(fill=tk.BOTH, expand=True) + + def save_habits(self): + self.tracker.save_to_file() + messagebox.showinfo("Success", "Habits saved successfully!") + + def run(self): + self.root.mainloop() + +if __name__ == "__main__": + app = HabitTrackerGUI() + app.run() diff --git a/habit_tracker/habit_tracker_terminal.py b/habit_tracker/habit_tracker_terminal.py new file mode 100644 index 00000000..7ca4adc5 --- /dev/null +++ b/habit_tracker/habit_tracker_terminal.py @@ -0,0 +1,274 @@ +import json +import datetime +import matplotlib.pyplot as plt +from collections import defaultdict +import calendar + +class Habit: + def __init__(self, name, category=None, goal_frequency="daily", goal_count=1): + self.name = name + self.category = category + self.history = {} + self.notes = defaultdict(str) + self.goal_frequency = goal_frequency # daily, weekly, monthly + self.goal_count = goal_count # number of times to complete per frequency + + def mark_completed(self, date=None): + date = date or datetime.datetime.now().strftime('%Y-%m-%d') + self.history[date] = True + + def add_note(self, date, note): + self.notes[date] = note + + def get_streak(self): + if not self.history: + return 0 + + dates = sorted(self.history.keys()) + current_date = datetime.datetime.now().date() + last_completion = datetime.datetime.strptime(dates[-1], '%Y-%m-%d').date() + + # If the last completion is not today or yesterday, streak is broken + if (current_date - last_completion).days > 1: + return 0 + + streak = 1 + for i in range(len(dates)-1, 0, -1): + date1 = datetime.datetime.strptime(dates[i], '%Y-%m-%d').date() + date2 = datetime.datetime.strptime(dates[i-1], '%Y-%m-%d').date() + if (date1 - date2).days == 1: + streak += 1 + else: + break + return streak + + def get_completion_rate(self, period="all"): + today = datetime.datetime.now().date() + total_days = 0 + completed_days = len(self.history) + + if period == "week": + start_date = today - datetime.timedelta(days=7) + completed_days = sum(1 for date in self.history if datetime.datetime.strptime(date, '%Y-%m-%d').date() >= start_date) + total_days = 7 + elif period == "month": + start_date = today.replace(day=1) + completed_days = sum(1 for date in self.history if datetime.datetime.strptime(date, '%Y-%m-%d').date() >= start_date) + total_days = calendar.monthrange(today.year, today.month)[1] + elif period == "all": + if self.history: + start_date = datetime.datetime.strptime(min(self.history.keys()), '%Y-%m-%d').date() + total_days = (today - start_date).days + 1 + + return (completed_days / total_days * 100) if total_days > 0 else 0 + + def get_goal_progress(self): + today = datetime.datetime.now().date() + completions = 0 + + if self.goal_frequency == "daily": + completions = 1 if today.strftime('%Y-%m-%d') in self.history else 0 + elif self.goal_frequency == "weekly": + week_start = today - datetime.timedelta(days=today.weekday()) + completions = sum(1 for date in self.history + if datetime.datetime.strptime(date, '%Y-%m-%d').date() >= week_start) + elif self.goal_frequency == "monthly": + month_start = today.replace(day=1) + completions = sum(1 for date in self.history + if datetime.datetime.strptime(date, '%Y-%m-%d').date() >= month_start) + + return (completions / self.goal_count * 100) if self.goal_count > 0 else 0 + + def __repr__(self): + return f"{self.name}: {len(self.history)} completions" + +class HabitTracker: + def __init__(self): + self.habits = {} + + def add_habit(self, name, category=None, goal_frequency="daily", goal_count=1): + if name not in self.habits: + self.habits[name] = Habit(name, category, goal_frequency, goal_count) + else: + print("Habit already exists.") + + def delete_habit(self, name): + if name in self.habits: + del self.habits[name] + else: + print("Habit does not exist.") + + def mark_completed(self, name, date=None): + if name in self.habits: + self.habits[name].mark_completed(date) + else: + print("Habit does not exist.") + + def add_note(self, name, date, note): + if name in self.habits: + self.habits[name].add_note(date, note) + else: + print("Habit does not exist.") + + def view_habits(self): + for habit in self.habits.values(): + print(habit) + + def get_habits_by_category(self): + categories = defaultdict(list) + for habit in self.habits.values(): + categories[habit.category or "Uncategorized"].append(habit.name) + return dict(categories) + + def get_streak(self, name): + if name in self.habits: + return self.habits[name].get_streak() + else: + print("Habit does not exist.") + return 0 + + def get_completion_stats(self, name, period="all"): + if name in self.habits: + return self.habits[name].get_completion_rate(period) + else: + print("Habit does not exist.") + return 0 + + def get_goal_progress(self, name): + if name in self.habits: + return self.habits[name].get_goal_progress() + else: + print("Habit does not exist.") + return 0 + + def save_to_file(self, filename='habits.json'): + data = {name: habit.history for name, habit in self.habits.items()} + with open(filename, 'w') as f: + json.dump(data, f) + print("Habits saved to file.") + + def load_from_file(self, filename='habits.json'): + try: + with open(filename, 'r') as f: + data = json.load(f) + self.habits = {name: Habit(name) for name in data} + for name, history in data.items(): + self.habits[name].history = history + print("Habits loaded from file.") + except FileNotFoundError: + print("File not found. Starting with an empty habit tracker.") + + def visualize_habit(self, name, view_type="timeline"): + if name in self.habits: + habit = self.habits[name] + dates = list(habit.history.keys()) + dates.sort() + + if not dates: + print("No data to visualize.") + return + + plt.figure(figsize=(12, 6)) + + if view_type == "timeline": + completions = [1 if date in dates else 0 for date in dates] + plt.plot(dates, completions, marker='o') + plt.title(f'Habit Timeline: {name}') + plt.xlabel('Date') + plt.ylabel('Completion') + + elif view_type == "heatmap": + # Create weekly heatmap + weeks = len(dates) // 7 + 1 + data = [[0] * 7 for _ in range(weeks)] + + for date in dates: + dt = datetime.datetime.strptime(date, '%Y-%m-%d') + week = (dt.date() - datetime.datetime.strptime(dates[0], '%Y-%m-%d').date()).days // 7 + weekday = dt.weekday() + if week < weeks and weekday < 7: + data[week][weekday] = 1 + + plt.imshow(data, cmap='YlOrRd') + plt.title(f'Habit Heatmap: {name}') + plt.xlabel('Day of Week') + plt.ylabel('Week') + plt.colorbar(label='Completion') + plt.xticks(range(7), ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']) + + plt.tight_layout() + plt.show() + else: + print("Habit does not exist.") + +def main(): + tracker = HabitTracker() + tracker.load_from_file() + + while True: + print("\nHabit Tracker") + print("1. Add Habit") + print("2. Delete Habit") + print("3. Mark Habit as Completed") + print("4. View All Habits") + print("5. Save Habits to File") + print("6. Load Habits from File") + print("7. Visualize Habit") + print("8. Add Note to Habit") + print("9. View Habit Statistics") + print("10. View Habits by Category") + print("11. View Goal Progress") + print("12. Exit") + choice = input("Choose an option: ") + + if choice == '1': + name = input("Enter habit name: ") + category = input("Enter habit category (or press Enter to skip): ") + frequency = input("Enter goal frequency (daily/weekly/monthly) [daily]: ") or "daily" + count = input("Enter goal count [1]: ") or "1" + tracker.add_habit(name, category or None, frequency, int(count)) + elif choice == '2': + name = input("Enter habit name to delete: ") + tracker.delete_habit(name) + elif choice == '3': + name = input("Enter habit name: ") + date = input("Enter date (YYYY-MM-DD) or press Enter for today: ") + tracker.mark_completed(name, date if date else None) + elif choice == '4': + tracker.view_habits() + elif choice == '5': + tracker.save_to_file() + elif choice == '6': + tracker.load_from_file() + elif choice == '7': + name = input("Enter habit name to visualize: ") + view_type = input("Enter view type (timeline/heatmap) [timeline]: ") or "timeline" + tracker.visualize_habit(name, view_type) + elif choice == '8': + name = input("Enter habit name: ") + date = input("Enter date (YYYY-MM-DD) or press Enter for today: ") + note = input("Enter note: ") + tracker.add_note(name, date if date else None, note) + elif choice == '9': + name = input("Enter habit name: ") + print(f"\nStreak: {tracker.get_streak(name)} days") + print(f"Week completion rate: {tracker.get_completion_stats(name, 'week'):.1f}%") + print(f"Month completion rate: {tracker.get_completion_stats(name, 'month'):.1f}%") + print(f"Overall completion rate: {tracker.get_completion_stats(name, 'all'):.1f}%") + elif choice == '10': + categories = tracker.get_habits_by_category() + for category, habits in categories.items(): + print(f"\n{category}:") + for habit in habits: + print(f" - {habit}") + elif choice == '11': + name = input("Enter habit name: ") + progress = tracker.get_goal_progress(name) + print(f"Current goal progress: {progress:.1f}%") + elif choice == '12': + break + else: + print("Invalid choice. Please try again.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/habit_tracker/requirements.txt b/habit_tracker/requirements.txt new file mode 100644 index 00000000..f247c429 --- /dev/null +++ b/habit_tracker/requirements.txt @@ -0,0 +1,3 @@ +matplotlib>=3.7.1,<4.0.0 +ttkthemes>=3.2.2 +pillow>=9.5.0 From 4b7608c8bb30d7e1eba294a8527a21875f9c3455 Mon Sep 17 00:00:00 2001 From: Shubham Kumar Date: Thu, 19 Jun 2025 10:19:17 +0530 Subject: [PATCH 2/2] Add: Habit Tracker project --- habit_tracker/README.md | 2 +- habit_tracker/assets/habi_tracker.png | Bin 0 -> 22503 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 habit_tracker/assets/habi_tracker.png diff --git a/habit_tracker/README.md b/habit_tracker/README.md index 8e4963da..1ed997d6 100644 --- a/habit_tracker/README.md +++ b/habit_tracker/README.md @@ -30,7 +30,7 @@ To stop the script, press `CTRL + C`. Below is a demo of the Habit Tracker in action: - + ## 🤖 Author diff --git a/habit_tracker/assets/habi_tracker.png b/habit_tracker/assets/habi_tracker.png new file mode 100644 index 0000000000000000000000000000000000000000..f3ca7de5889e31c36c522de628ad122bf4ea2149 GIT binary patch literal 22503 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