From 9c9aa59cf42baca9d7adb1ceea9e8aa445259354 Mon Sep 17 00:00:00 2001
From: Eleftherios Angelos Tsourdiou <eleftherios2.tsourdiou@live.uwe.ac.uk>
Date: Mon, 17 Mar 2025 15:58:55 +0000
Subject: [PATCH] Almost Done

---
 data_handler.py |  22 ++++++
 main.py         | 189 +++++++++++++++---------------------------------
 ui_design.py    | 100 +++++++++++++++++++++++++
 visualizer.py   |  57 +++++++++++++++
 4 files changed, 237 insertions(+), 131 deletions(-)

diff --git a/data_handler.py b/data_handler.py
index e69de29..d6789b4 100644
--- a/data_handler.py
+++ b/data_handler.py
@@ -0,0 +1,22 @@
+import pandas as pd
+
+import pandas as pd
+
+def load_csv(file_path):
+    if not isinstance(file_path, str) or not file_path.endswith('.csv'):
+        raise ValueError("Invalid file path provided for CSV loading.")
+
+    df = pd.read_csv(file_path).fillna("None")
+    if "referral" in df.columns:
+        df["referral"] = df["referral"].replace({1.0: "Need Referral", 0.0: "No Need for Referral"})
+    
+    return df
+
+
+def filter_referrals(df):
+    if "referral" in df.columns:
+        return df[df["referral"] == "Need Referral"].copy()
+    return df
+
+def reset_table(df):
+    return df.copy()
\ No newline at end of file
diff --git a/main.py b/main.py
index 4881612..d120c57 100644
--- a/main.py
+++ b/main.py
@@ -1,159 +1,86 @@
 import sys
-import pandas as pd
-from PyQt6.QtWidgets import (
-    QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton,
-    QFileDialog, QLabel, QTableWidget, QTableWidgetItem, QTabWidget, QTextEdit
-)
+from PyQt6.QtWidgets import QApplication, QMainWindow, QTabWidget, QFileDialog, QTableWidgetItem
 from PyQt6.QtGui import QColor
-from PyQt6.QtCore import Qt
-import matplotlib.pyplot as plt
-from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
+from ui_design import create_upload_tab, create_dashboard_tab, create_analytics_tab, create_help_tab
+from data_handler import load_csv, filter_referrals, reset_table
+from visualizer import display_graphs
 
-
-class FeedingDashboard(QMainWindow):
+class CCUDashboard(QMainWindow):
     def __init__(self):
         super().__init__()
-
-        self.setWindowTitle("Feeding Dashboard")
-        self.setGeometry(100, 100, 1000, 600)
-
-        # Main Tab Widget
+        
+        self.setWindowTitle("CCU Patients Data Dashboard")
+        self.setGeometry(100, 100, 1500, 1000)
+        
         self.tabs = QTabWidget()
         self.setCentralWidget(self.tabs)
-
-        # Create Tabs
-        self.upload_tab = QWidget()
-        self.dashboard_tab = QWidget()
-        self.analytics_tab = QWidget()
-        self.help_tab = QWidget()
-
-        # Add tabs to the tab widget
-        self.tabs.addTab(self.upload_tab, "Upload CSV File")
+        
+        self.df = None  # DataFrame to store loaded data
+        self.filtered_df = None  # Copy of data for filtering
+        
+        # Initialize tabs using imported functions
+        self.csvuploading_tab = create_upload_tab(self)
+        self.dashboard_tab = create_dashboard_tab(self)
+        self.analytics_tab = create_analytics_tab(self)
+        self.help_tab = create_help_tab()
+        
+        # Add tabs to the interface
+        self.tabs.addTab(self.csvuploading_tab, "Upload CSV File")
         self.tabs.addTab(self.dashboard_tab, "View Dashboard")
         self.tabs.addTab(self.analytics_tab, "Analytics")
         self.tabs.addTab(self.help_tab, "Help")
 
-        # Initialize tab UI
-        self.init_upload_tab()
-        self.init_dashboard_tab()
-        self.init_analytics_tab()
-        self.init_help_tab()
-
-        self.df = None  # Store the loaded data
-
-    def init_upload_tab(self):
-        """Upload CSV File Tab"""
-        layout = QVBoxLayout()
-
-        self.upload_button = QPushButton("Upload CSV File")
-        self.upload_button.clicked.connect(self.load_csv)
-
-        self.file_label = QLabel("No file selected")
-
-        layout.addWidget(self.upload_button)
-        layout.addWidget(self.file_label)
-
-        self.upload_tab.setLayout(layout)
-
-    def init_dashboard_tab(self):
-        """Dashboard Tab - Displays CSV Data in a Table"""
-        layout = QVBoxLayout()
-
-        self.table_widget = QTableWidget()
-        layout.addWidget(self.table_widget)
-
-        self.dashboard_tab.setLayout(layout)
-
-    def init_analytics_tab(self):
-        """Analytics Tab - Displays Graphs"""
-        layout = QVBoxLayout()
-
-        self.canvas = FigureCanvas(plt.figure(figsize=(6, 4)))
-        layout.addWidget(self.canvas)
-
-        self.analytics_tab.setLayout(layout)
-
-    def init_help_tab(self):
-        """Help Tab - Displays Instructions"""
-        layout = QVBoxLayout()
-
-        help_text = QTextEdit()
-        help_text.setReadOnly(True)
-        help_text.setText(
-            "Welcome to the Feeding Dashboard!\n\n"
-            "1. Upload a CSV file in the 'Upload CSV File' tab.\n"
-            "2. View patient data in the 'View Dashboard' tab.\n"
-            "3. Analyze trends in the 'Analytics' tab.\n"
-            "4. Refer to this guide for help.\n"
-        )
-
-        layout.addWidget(help_text)
-        self.help_tab.setLayout(layout)
-
-    def load_csv(self):
-        """Load CSV File"""
+    def load_csv_file(self):
+        """Load the CSV file and update the UI."""
         file_path, _ = QFileDialog.getOpenFileName(self, "Open CSV", "", "CSV Files (*.csv)")
-        if file_path:
-            self.file_label.setText(f"Loaded: {file_path}")
-            self.df = pd.read_csv(file_path)
-            self.df = self.df.fillna("None")  #handle empty values
-
-            
-            if "referral" in self.df.columns:
-                self.df["referral"] = self.df["referral"].replace({1.0: "Need Referral", 0.0: "No Need for Referral"})
+        
+        if not file_path:  # Ensure a valid file path is selected
+            print("No file selected.")
+            return
+        
+        self.file_label.setText(f"You have succesfully uploaded the CSV: {file_path}")
+        self.df = load_csv(file_path)
+        self.filtered_df = self.df.copy()
+        self.display_data()
+        display_graphs(self.df, self.analytics_tab)  # Update graphs
+
+    def filter_referrals(self):
+        """Filter to show only patients needing referral."""
+        if self.df is not None:
+            self.filtered_df = filter_referrals(self.df)
+            self.display_data()
 
+    def reset_table(self):
+        """Reset the table to show all patients."""
+        if self.df is not None:
+            self.filtered_df = reset_table(self.df)
             self.display_data()
-            self.display_graphs()
 
     def display_data(self):
-        """Display CSV Data in Dashboard Table"""
-        if self.df is None:
+        """Update the table with the current data."""
+        if self.filtered_df is None:
             return
 
-        self.table_widget.setRowCount(self.df.shape[0])
-        self.table_widget.setColumnCount(self.df.shape[1])
-        self.table_widget.setHorizontalHeaderLabels(self.df.columns)
-
-        for row in range(self.df.shape[0]):
-            for col in range(self.df.shape[1]):
-                cell_value = str(self.df.iat[row, col])
-                if cell_value.strip() == "":
-                    cell_value = "None"
+        self.table_widget.setRowCount(self.filtered_df.shape[0])
+        self.table_widget.setColumnCount(self.filtered_df.shape[1])
+        self.table_widget.setHorizontalHeaderLabels(self.filtered_df.columns)
 
+        for row in range(self.filtered_df.shape[0]):
+            for col in range(self.filtered_df.shape[1]):
+                cell_value = str(self.filtered_df.iat[row, col])
                 item = QTableWidgetItem(cell_value)
-
-                # highlight Referral Patients
-                if "referral" in self.df.columns and self.df.columns[col] == "referral":
+                
+                # Modify the referral column to identify with red and green colour the outcome
+                if "referral" in self.filtered_df.columns and self.filtered_df.columns[col] == "referral":
                     if cell_value == "Need Referral":
-                        item.setBackground(QColor(255, 102, 102))  # light Red
+                        item.setBackground(QColor(255, 102, 102))  # Red for need Referral
                     elif cell_value == "No Need for Referral":
-                        item.setBackground(QColor(144, 238, 144))  # light Green
-
+                        item.setBackground(QColor(144, 238, 144))  # Green for No Need for Referral
+                
                 self.table_widget.setItem(row, col, item)
 
-    def display_graphs(self):
-        """Display Analytics Graphs"""
-        if self.df is None or "referral" not in self.df.columns:
-            return
-
-        # Count referrals
-        referral_counts = self.df["referral"].value_counts()
-
-        # Clear previous graph
-        self.canvas.figure.clear()
-
-        # Create new graph
-        ax = self.canvas.figure.add_subplot(111)
-        ax.bar(referral_counts.index, referral_counts.values, color=["green", "red"])
-        ax.set_title("Patient Referrals")
-        ax.set_ylabel("Count")
-
-        # Update canvas
-        self.canvas.draw()
-
-
 if __name__ == "__main__":
     app = QApplication(sys.argv)
-    window = FeedingDashboard()
+    window = CCUDashboard()
     window.show()
     sys.exit(app.exec())
diff --git a/ui_design.py b/ui_design.py
index e69de29..20b0ea0 100644
--- a/ui_design.py
+++ b/ui_design.py
@@ -0,0 +1,100 @@
+from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QTableWidget, QTextEdit, QHBoxLayout
+
+def create_upload_tab(parent):
+    tab = QWidget()
+    layout = QVBoxLayout()
+    parent.upload_button = QPushButton("Upload CSV File")
+    parent.upload_button.clicked.connect(parent.load_csv_file)
+    parent.file_label = QLabel("No file selected")
+    layout.addWidget(parent.upload_button)
+    layout.addWidget(parent.file_label)
+    tab.setLayout(layout)
+    return tab
+
+def create_dashboard_tab(parent):
+    tab = QWidget()
+    layout = QVBoxLayout()
+    parent.table_widget = QTableWidget()
+    layout.addWidget(parent.table_widget)
+    
+    button_layout = QHBoxLayout()
+    parent.referral_button = QPushButton("Need Referral")
+    parent.referral_button.clicked.connect(parent.filter_referrals)
+    parent.reset_button = QPushButton("Show All")
+    parent.reset_button.clicked.connect(parent.reset_table)
+    
+    button_layout.addWidget(parent.referral_button)
+    button_layout.addWidget(parent.reset_button)
+    layout.addLayout(button_layout)
+    
+    tab.setLayout(layout)
+    return tab
+
+def create_analytics_tab(parent):
+    tab = QWidget()
+    layout = QVBoxLayout()
+    parent.patient_count_label = QLabel("Total Patients: 0")
+    layout.addWidget(parent.patient_count_label)
+    tab.setLayout(layout)
+    return tab
+
+def create_help_tab():
+    tab = QWidget()
+    layout = QVBoxLayout()
+    help_text = QTextEdit()
+    help_text.setReadOnly(True)
+    help_text.setStyleSheet("background-color: #EAEAEA; color: #222222; padding: 10px; border-radius: 8px;")
+    help_text.setHtml("""
+    <html>
+    <head>
+        <style>
+            h1 { font-size: 22px; font-weight: bold; color: #333366; }
+            h2 { font-size: 18px; font-weight: bold; color: #444488; margin-top: 15px; }
+            p { font-size: 14px; color: #222222; margin-left: 10px; }
+            ul { font-size: 14px; color: #222222; margin-left: 20px; }
+        </style>
+    </head>
+    <body>
+        <h1>How to Use the Feeding Dashboard</h1>
+        
+        <p>This program is designed and developed for the CCU staff in order to help monitor patients and identify which patients need referral to a dietitian. 
+        Below are the instructions for each tab:</p>
+        
+        <h2>1. Upload CSV File Tab</h2>
+        <ul>
+            <li>Click the <b>"Upload CSV File"</b> button.</li>
+            <li>Select a CSV file containing patient data.</li>
+            <li>The file will be loaded (it can take a while depending on the file size) and now you can see a list with all the patients and their data on the View Dashboard Tab</li>
+        </ul>
+        
+        <h2>2. View Dashboard Tab</h2>
+        <ul>
+            <li>Displays a table of all admitted patients.</li>
+            <li>Click <b>"Need Referral"</b> to filter and show only patients requiring referral.</li>
+            <li>Click <b>"Show All"</b> to reset the table and display all patients.</li>
+        </ul>
+        
+        <h2>3. Analytics Tab</h2>
+        <ul>
+            <li>Shows a <b>total count of current patients</b>.</li>
+            <li>Displays a <b>pie chart</b> with the percentage and exact number of patients who need and don’t need referral.</li>
+            <li>Displays a <b>bar chart</b> for missing patient data, showing the exact number of missing entries for each category.</li>
+        </ul>
+        
+        <h2>4. Help Tab</h2>
+        <ul>
+            <li>Provides an overview of how to use the application.</li>
+        
+        </ul>
+        
+        <h2>Additional Notes</h2>
+        <ul>
+            <li>Make sure that the CSV files are not corrupted and include ideally all the data for the best results. </li>
+            <li>The graphs and charts on the analytics tab, refresh automatically with every new csv upload.</li>
+        </ul>
+    </body>
+    </html>
+    """)
+    layout.addWidget(help_text)
+    tab.setLayout(layout)
+    return tab
\ No newline at end of file
diff --git a/visualizer.py b/visualizer.py
index e69de29..58a93d0 100644
--- a/visualizer.py
+++ b/visualizer.py
@@ -0,0 +1,57 @@
+import matplotlib.pyplot as plt
+import pandas as pd
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
+from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QTableWidget, QTextEdit, QHBoxLayout
+
+def display_graphs(df, analytics_tab):
+    if df is None or "referral" not in df.columns:
+        return
+
+    # Find patient_count_label before clearing widgets
+    patient_count_label = None
+    for i in reversed(range(analytics_tab.layout().count())):
+        widget = analytics_tab.layout().itemAt(i).widget()
+        if isinstance(widget, QLabel) and "Total Patients" in widget.text():
+            patient_count_label = widget  # Store reference to patient count label
+        else:
+            widget.deleteLater()  # Remove only charts
+
+    # If patient_count_label exists, update its text
+    if patient_count_label:
+        patient_count_label.setText(f"Total Patients: {len(df)}")
+    else:
+        patient_count_label = QLabel(f"Total Patients: {len(df)}")
+        analytics_tab.layout().addWidget(patient_count_label)  # Re-add if deleted
+
+    # Add new graphs
+    analytics_tab.layout().addWidget(create_pie_chart(df["referral"].value_counts()))
+    analytics_tab.layout().addWidget(create_missing_data_chart(df))
+
+
+
+
+def create_pie_chart(data):
+    fig, ax = plt.subplots(figsize=(6, 4))
+    labels = [f"{label} ({count})" for label, count in zip(data.index, data.values)]
+    ax.pie(data.values, labels=labels, autopct='%1.1f%%', startangle=90, colors=["green", "red"])
+    ax.set_title("Referral Distribution")
+    return FigureCanvas(fig)
+
+def create_missing_data_chart(df):
+    missing_counts = df.replace("None", pd.NA).isna().sum()
+    missing_counts = missing_counts[missing_counts > 0]
+
+    fig, ax = plt.subplots(figsize=(10, 6))
+    bars = ax.bar(missing_counts.index, missing_counts.values, color="blue")
+
+    ax.set_xticks(range(len(missing_counts)))  # Ensure tick positions match the labels
+    ax.set_xticklabels(missing_counts.index.tolist(), rotation=30, ha='right', fontsize=9)
+
+    for bar in bars:
+        height = bar.get_height()
+        ax.text(bar.get_x() + bar.get_width()/2, height, str(int(height)), ha='center', va='bottom', fontsize=10, color='black')
+
+    ax.set_title("Patients Without Data")
+    ax.set_ylabel("Number of Patients")
+
+    return FigureCanvas(fig)
-- 
GitLab