Files
thechart/src/graph_manager.py

150 lines
5.2 KiB
Python

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)