Files
thechart/src/main.py
2025-07-17 12:33:51 -07:00

522 lines
18 KiB
Python

import csv
import os
import tkinter as tk
from tkinter import messagebox, ttk
import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class MedTrackerApp:
def __init__(self, root):
self.root = root
self.root.resizable(True, True)
self.root.title("Thechart - medication tracker")
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
# self.root.iconbitmap("app_icon.ico")
# self.root.geometry("800x600")
self.filename = "thechart_data.csv"
self.initialize_csv()
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0)
# --- Input Frame ---
input_frame = ttk.LabelFrame(main_frame, text="New Entry")
input_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
ttk.Label(input_frame, text="Depression (0-10):").grid(
row=0, column=0, sticky="w", padx=5, pady=2
)
self.depression_var = tk.IntVar()
ttk.Scale(
input_frame,
from_=0,
to=10,
orient=tk.HORIZONTAL,
variable=self.depression_var,
).grid(row=0, column=1, sticky="ew")
ttk.Label(input_frame, text="Anxiety (0-10):").grid(
row=1, column=0, sticky="w", padx=5, pady=2
)
self.anxiety_var = tk.IntVar()
ttk.Scale(
input_frame, from_=0, to=10, orient=tk.HORIZONTAL, variable=self.anxiety_var
).grid(row=1, column=1, sticky="ew")
ttk.Label(input_frame, text="Sleep Quality (0-10):").grid(
row=2, column=0, sticky="w", padx=5, pady=2
)
self.sleep_var = tk.IntVar()
ttk.Scale(
input_frame, from_=0, to=10, orient=tk.HORIZONTAL, variable=self.sleep_var
).grid(row=2, column=1, sticky="ew")
ttk.Label(input_frame, text="Appetite (0-10):").grid(
row=3, column=0, sticky="w", padx=5, pady=2
)
self.appetite_var = tk.IntVar()
ttk.Scale(
input_frame,
from_=0,
to=10,
orient=tk.HORIZONTAL,
variable=self.appetite_var,
).grid(row=3, column=1, sticky="ew")
ttk.Label(input_frame, text="Treatment:").grid(
row=4, column=0, sticky="w", padx=5, pady=2
)
medicine_frame = ttk.LabelFrame(input_frame, text="Medicine")
medicine_frame.grid(row=4, column=1, padx=0, pady=10, sticky="nsew")
self.bupropion_var = tk.IntVar(value=0)
self.hydroxyzine_var = tk.IntVar(value=0)
self.gabapentin_var = tk.IntVar(value=0)
self.propranolol_var = tk.IntVar(value=0)
ttk.Checkbutton(
medicine_frame,
text="Bupropion 150mg",
variable=self.bupropion_var,
name="bupropion_check",
command=lambda: self.toggle_checkbox(obj_name="bupropion_check"),
).grid(row=0, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Hydroxyzine 25mg",
variable=self.hydroxyzine_var,
name="hydroxyzine_check",
command=lambda: self.toggle_checkbox(obj_name="hydroxyzine_check"),
).grid(row=1, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Gabapentin 100mg",
variable=self.gabapentin_var,
name="gabapentin_check",
command=lambda: self.toggle_checkbox(obj_name="gabapentin_check"),
).grid(row=2, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Propranolol 10mg",
name="propranolol_check",
variable=self.propranolol_var,
command=lambda: self.toggle_checkbox(obj_name="propranolol_check"),
).grid(row=3, column=0, sticky="w", padx=5, pady=2)
ttk.Label(input_frame, text="Note:").grid(
row=5, column=0, sticky="w", padx=5, pady=2
)
self.note_var = tk.StringVar()
ttk.Entry(input_frame, textvariable=self.note_var).grid(
row=5, column=1, sticky="ew", padx=5, pady=2
)
ttk.Label(input_frame, text="Date (mm/dd/yyyy):").grid(
row=6, column=0, sticky="w", padx=5, pady=2
)
self.date_var = tk.StringVar()
ttk.Entry(input_frame, textvariable=self.date_var, justify="center").grid(
row=6, column=1, sticky="ew", padx=5, pady=2
)
button_frame = ttk.Frame(input_frame)
button_frame.grid(row=7, column=0, columnspan=2, pady=10)
ttk.Button(button_frame, text="Add Entry", command=self.add_entry).pack(
side="left", padx=5
)
ttk.Button(button_frame, text="Quit", command=self.on_closing).pack(
side="left", padx=5
)
# --- Table Frame ---
table_frame = ttk.LabelFrame(main_frame, text="Log (Double-click to edit)")
table_frame.grid(row=1, column=1, padx=10, pady=10, sticky="nsew")
self.tree = ttk.Treeview(
table_frame,
columns=(
"Date",
"Depression",
"Anxiety",
"Sleep",
"Appetite",
"Bupropion",
"Hydroxyzine",
"Gabapentin",
"Propranolol",
"Note",
),
show="headings",
)
self.tree.heading("Date", text="Date")
self.tree.heading("Depression", text="Depression")
self.tree.heading("Anxiety", text="Anxiety")
self.tree.heading("Sleep", text="Sleep")
self.tree.heading("Appetite", text="Appetite")
self.tree.heading("Bupropion", text="Bupropion 150mg")
self.tree.heading("Hydroxyzine", text="Hydroxyzine 25mg")
self.tree.heading("Gabapentin", text="Gabapentin 100mg")
self.tree.heading("Propranolol", text="Propranolol 10mg")
self.tree.heading("Note", text="Note")
self.tree.column("Date", width=80, anchor="center")
self.tree.column("Depression", width=80, anchor="center")
self.tree.column("Anxiety", width=80, anchor="center")
self.tree.column("Sleep", width=80, anchor="center")
self.tree.column("Appetite", width=80, anchor="center")
self.tree.column("Bupropion", width=120, anchor="center")
self.tree.column("Hydroxyzine", width=120, anchor="center")
self.tree.column("Gabapentin", width=120, anchor="center")
self.tree.column("Propranolol", width=120, anchor="center")
self.tree.column("Note", width=300, anchor="w")
# --- Bind double-click event ---
self.tree.bind("<Double-1>", self.on_double_click)
self.tree.pack(side="left", fill="both", expand=True)
scrollbar = ttk.Scrollbar(
table_frame, orient="vertical", command=self.tree.yview
)
self.tree.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
# --- Graph Frame ---
graph_frame = ttk.LabelFrame(main_frame, text="Evolution")
graph_frame.grid(row=0, column=0, columnspan=2, padx=10, pady=10, sticky="nsew")
self.fig, self.ax = plt.subplots()
self.canvas = FigureCanvasTkAgg(figure=self.fig, master=graph_frame)
self.canvas.get_tk_widget().pack(fill="both", expand=True)
self.load_data()
def toggle_checkbox(obj_name: str) -> None:
if ttk.Checkbutton.nametowidget(name=obj_name).get():
ttk.Checkbutton.nametowidget(name=obj_name).set(False)
else:
ttk.Checkbutton.nametowidget(name=obj_name).set(True)
def on_double_click(self, event: any) -> None:
"""Handle double-click event to edit an entry."""
if len(self.tree.get_children()) > 0:
item_id = self.tree.selection()[0]
item_values = self.tree.item(item_id, "values")
self.create_edit_window(item_id, item_values)
def create_edit_window(self, item_id: str, values: tuple) -> None:
"""Create a new Toplevel window for editing an entry."""
edit_win = tk.Toplevel(self.root)
edit_win.title("Edit Entry")
# Unpack values
date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values
# Create variables for the widgets
date_var = tk.StringVar(value=str(date))
dep_var = tk.IntVar(value=int(dep))
anx_var = tk.IntVar(value=int(anx))
slp_var = tk.IntVar(value=int(slp))
app_var = tk.IntVar(value=int(app))
bup_var = tk.IntVar(value=int(bup))
hydro_var = tk.IntVar(value=int(hydro))
gaba_var = tk.IntVar(value=int(gaba))
prop_var = tk.IntVar(value=int(prop))
note_var = tk.StringVar(value=str(note))
# Create form widgets
ttk.Label(edit_win, text="Depression:").grid(
row=1, column=0, sticky="w", padx=5, pady=2
)
ttk.Scale(
edit_win, from_=1, to=10, variable=dep_var, orient=tk.HORIZONTAL
).grid(row=1, column=1, sticky="ew")
ttk.Label(edit_win, text="Anxiety:").grid(
row=2, column=0, sticky="w", padx=5, pady=2
)
ttk.Scale(
edit_win, from_=1, to=10, variable=anx_var, orient=tk.HORIZONTAL
).grid(row=2, column=1, sticky="ew")
ttk.Label(edit_win, text="Sleep:").grid(
row=3, column=0, sticky="w", padx=5, pady=2
)
ttk.Scale(
edit_win, from_=1, to=10, variable=slp_var, orient=tk.HORIZONTAL
).grid(row=3, column=1, sticky="ew")
ttk.Label(edit_win, text="Appetite:").grid(
row=4, column=0, sticky="w", padx=5, pady=2
)
ttk.Scale(
edit_win, from_=1, to=10, variable=app_var, orient=tk.HORIZONTAL
).grid(row=4, column=1, sticky="ew")
ttk.Label(edit_win, text="Treatment:").grid(
row=5, column=0, sticky="w", padx=5, pady=2
)
medicine_frame = ttk.LabelFrame(edit_win, text="Medicine")
medicine_frame.grid(row=5, column=1, padx=0, pady=10, sticky="nsew")
ttk.Checkbutton(
medicine_frame,
text="Bupropion 150mg",
name="bupropion_check",
variable=bup_var,
command=lambda: self.toggle_checkbox(obj_name="bupropion_check"),
).grid(row=0, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Hydroxyzine 25mg",
name="hydroxyzine_check",
variable=hydro_var,
command=lambda: self.toggle_checkbox(obj_name="hydroxyzine_check"),
).grid(row=1, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Gabapentin 100mg",
name="gabapentin_check",
variable=gaba_var,
command=lambda: self.toggle_checkbox(obj_name="gabapentin_check"),
).grid(row=2, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Propranolol 10mg",
name="propranolol_check",
variable=prop_var,
command=lambda: self.toggle_checkbox(obj_name="propranolol_check"),
).grid(row=3, column=0, sticky="w", padx=5, pady=2)
ttk.Label(edit_win, text="Note:").grid(
row=6, column=0, sticky="w", padx=5, pady=2
)
ttk.Entry(edit_win, textvariable=note_var).grid(row=6, column=1, sticky="ew")
ttk.Label(edit_win, text="Date:").grid(
row=7, column=0, sticky="w", padx=5, pady=2
)
ttk.Entry(edit_win, textvariable=date_var).grid(row=7, column=1, sticky="ew")
# Save and Cancel buttons
save_btn = ttk.Button(
edit_win,
text="Save",
command=lambda: self.save_edit(
edit_win,
date_var.get(),
dep_var.get(),
anx_var.get(),
slp_var.get(),
app_var.get(),
bup_var.get(),
hydro_var.get(),
gaba_var.get(),
prop_var.get(),
note_var.get(),
),
)
save_btn.grid(row=8, column=0, padx=5, pady=10)
cancel_btn = ttk.Button(edit_win, text="Cancel", command=edit_win.destroy)
cancel_btn.grid(row=8, column=1, padx=5, pady=10)
delete_btn = ttk.Button(
edit_win,
text="Delete",
command=lambda: self.delete_entry(edit_win, item_id),
)
delete_btn.grid(row=8, column=2, padx=5, pady=10)
def save_edit(
self,
edit_win: tk.Toplevel,
date: str,
dep: int,
anx: int,
slp: int,
app: int,
bup: int,
hydro: int,
gaba: int,
prop: int,
note: str,
) -> None:
"""
Save the edited data to the CSV file.
"""
df = pd.read_csv(self.filename)
# Find the row to update using the date as a unique identifier
df.loc[
df["date"] == date,
[
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"hydroxyzine",
"gabapentin",
"propranolol",
"note",
],
] = [date, dep, anx, slp, app, bup, hydro, gaba, prop, note]
# Write the updated dataframe back to the CSV
df.to_csv(self.filename, index=False)
edit_win.destroy()
messagebox.showinfo("Success", "Entry updated successfully!")
self.clear_entries()
self.load_data()
def on_closing(self) -> None:
if messagebox.askokcancel("Quit", "Do you want to quit the application?"):
plt.close(self.fig)
self.root.destroy()
def initialize_csv(self) -> None:
if not os.path.exists(self.filename):
with open(self.filename, mode="w", newline="") as file:
writer = csv.writer(file)
writer.writerow(
[
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"hydroxyzine",
"gabapentin",
"propranolol",
"note",
]
)
def add_entry(self) -> None:
with open(self.filename, mode="a", newline="") as file:
writer = csv.writer(file)
writer.writerow(
[
self.date_var.get(),
self.depression_var.get(),
self.anxiety_var.get(),
self.sleep_var.get(),
self.appetite_var.get(),
self.bupropion_var.get(),
self.hydroxyzine_var.get(),
self.gabapentin_var.get(),
self.propranolol_var.get(),
self.note_var.get(),
]
)
messagebox.showinfo("Success", "Entry added successfully!")
self.clear_entries()
self.load_data()
def delete_entry(self, edit_win: tk.Toplevel, item_id: str) -> None:
"""
Delete the selected entry from the CSV file.
"""
if messagebox.askyesno(
"Delete Entry", "Are you sure you want to delete this entry?"
):
df = pd.read_csv(self.filename)
# Get the date of the entry to delete
date = self.tree.item(item_id, "values")[0]
# Remove the row with the matching date
df = df[df["date"] != date]
# Write the updated dataframe back to the CSV
df.to_csv(self.filename, index=False)
edit_win.destroy()
messagebox.showinfo("Success", "Entry deleted successfully!")
self.load_data()
def clear_entries(self) -> None:
self.date_var.set("")
self.depression_var.set(0)
self.anxiety_var.set(0)
self.sleep_var.set(0)
self.appetite_var.set(0)
self.bupropion_var.set(False)
self.hydroxyzine_var.set(False)
self.gabapentin_var.set(False)
self.propranolol_var.set(False)
self.note_var.set("")
def load_data(self) -> None:
for i in self.tree.get_children():
self.tree.delete(i)
if os.path.exists(self.filename) and os.path.getsize(self.filename) > 0:
try:
df = pd.read_csv(
self.filename,
dtype={
"depression": int,
"anxiety": int,
"sleep": int,
"appetite": int,
"bupropion": int,
"hydroxyzine": int,
"gabapentin": int,
"propranolol": int,
"note": str,
"date": str,
},
).fillna("")
df = df.sort_values(by="date").reset_index(drop=True)
for index, row in df.iterrows():
self.tree.insert(parent="", index="end", values=list(row))
self.update_graph(df)
except pd.errors.EmptyDataError:
self.update_graph(pd.DataFrame())
def update_graph(self, df: pd.DataFrame) -> None:
self.ax.clear()
if not df.empty:
df = df.sort_values(by="date")
df.set_index(keys="date", inplace=True)
self.ax.plot(
df.index,
df["depression"],
marker="o",
linestyle="-",
label="Depression",
)
self.ax.plot(
df.index, df["anxiety"], marker="o", linestyle="-", label="Anxiety"
)
self.ax.plot(
df.index, df["sleep"], marker="o", linestyle="-", label="Sleep"
)
self.ax.plot(
df.index, df["appetite"], marker="o", linestyle="-", label="Appetite"
)
self.ax.legend()
self.ax.set_title("Medication Effects Over Time")
self.ax.set_xlabel("Date")
self.ax.set_ylabel("Rating (0-10)")
self.fig.autofmt_xdate()
self.canvas.draw()
if __name__ == "__main__":
root = tk.Tk()
app = MedTrackerApp(root)
root.mainloop()