diff --git a/habit_tracker/README.md b/habit_tracker/README.md new file mode 100644 index 00000000..1ed997d6 --- /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 00000000..00bb85f4 Binary files /dev/null and b/habit_tracker/__pycache__/habit_tracker.cpython-313.pyc differ diff --git a/habit_tracker/__pycache__/habit_tracker_terminal.cpython-313.pyc b/habit_tracker/__pycache__/habit_tracker_terminal.cpython-313.pyc new file mode 100644 index 00000000..177d3db6 Binary files /dev/null and b/habit_tracker/__pycache__/habit_tracker_terminal.cpython-313.pyc differ diff --git a/habit_tracker/assets/habi_tracker.png b/habit_tracker/assets/habi_tracker.png new file mode 100644 index 00000000..f3ca7de5 Binary files /dev/null and b/habit_tracker/assets/habi_tracker.png differ 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