import tkinter as tk from tkinter import ttk import matplotlib.figure import matplotlib.pyplot as plt import pandas as pd from matplotlib.axes import Axes from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg class GraphManager: """Handle all graph-related operations for the application.""" def __init__(self, parent_frame: ttk.LabelFrame) -> None: self.parent_frame: ttk.LabelFrame = parent_frame # Configure graph frame to expand self.parent_frame.grid_rowconfigure(0, weight=1) self.parent_frame.grid_columnconfigure(0, weight=1) # Initialize toggle variables for chart elements self.toggle_vars: dict[str, tk.BooleanVar] = { "depression": tk.BooleanVar(value=True), "anxiety": tk.BooleanVar(value=True), "sleep": tk.BooleanVar(value=True), "appetite": tk.BooleanVar(value=True), } # Create control frame for toggles self.control_frame: ttk.Frame = ttk.Frame(self.parent_frame) self.control_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5) # Create toggle checkboxes self._create_toggle_controls() # Create graph frame self.graph_frame: ttk.Frame = ttk.Frame(self.parent_frame) self.graph_frame.grid(row=1, column=0, sticky="nsew", padx=5, pady=5) # Reconfigure parent frame for new layout self.parent_frame.grid_rowconfigure(1, weight=1) self.parent_frame.grid_columnconfigure(0, weight=1) # Initialize matplotlib figure and canvas self.fig: matplotlib.figure.Figure self.ax: Axes self.fig, self.ax = plt.subplots() self.canvas: FigureCanvasTkAgg = FigureCanvasTkAgg( figure=self.fig, master=self.graph_frame ) self.canvas.get_tk_widget().pack(fill="both", expand=True) # Store current data for replotting self.current_data: pd.DataFrame = pd.DataFrame() def _create_toggle_controls(self) -> None: """Create toggle controls for chart elements.""" ttk.Label(self.control_frame, text="Show/Hide Elements:").pack( side="left", padx=5 ) toggle_configs = [ ("depression", "Depression"), ("anxiety", "Anxiety"), ("sleep", "Sleep"), ("appetite", "Appetite"), ] for key, label in toggle_configs: checkbox = ttk.Checkbutton( self.control_frame, text=label, variable=self.toggle_vars[key], command=self._on_toggle_changed, ) checkbox.pack(side="left", padx=5) def _on_toggle_changed(self) -> None: """Handle toggle changes by replotting the graph.""" if not self.current_data.empty: self._plot_graph_data(self.current_data) def update_graph(self, df: pd.DataFrame) -> None: """Update the graph with new data.""" self.current_data = df.copy() if not df.empty else pd.DataFrame() self._plot_graph_data(df) def _plot_graph_data(self, df: pd.DataFrame) -> None: """Plot the graph data with current toggle settings.""" self.ax.clear() if not df.empty: # Convert dates and sort df = df.copy() # Create a copy to avoid modifying the original df["date"] = pd.to_datetime(df["date"]) df = df.sort_values(by="date") df.set_index(keys="date", inplace=True) # Track if any series are plotted has_plotted_series = False # Plot data series based on toggle states if self.toggle_vars["depression"].get(): self._plot_series( df, "depression", "Depression (0:good, 10:bad)", "o", "-" ) has_plotted_series = True if self.toggle_vars["anxiety"].get(): self._plot_series(df, "anxiety", "Anxiety (0:good, 10:bad)", "o", "-") has_plotted_series = True if self.toggle_vars["sleep"].get(): self._plot_series(df, "sleep", "Sleep (0:bad, 10:good)", "o", "dashed") has_plotted_series = True if self.toggle_vars["appetite"].get(): self._plot_series( df, "appetite", "Appetite (0:bad, 10:good)", "o", "dashed" ) has_plotted_series = True # Configure graph appearance if has_plotted_series: 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() # Redraw the canvas self.canvas.draw() def _plot_series( self, df: pd.DataFrame, column: str, label: str, marker: str, linestyle: str, ) -> None: """Helper method to plot a data series.""" self.ax.plot( df.index, df[column], marker=marker, linestyle=linestyle, label=label, ) def close(self) -> None: """Clean up resources.""" plt.close(self.fig)