diff --git a/src/midrc_react/core/excel_layout.py b/src/midrc_react/core/excel_layout.py index c5446f3..d88dba5 100644 --- a/src/midrc_react/core/excel_layout.py +++ b/src/midrc_react/core/excel_layout.py @@ -50,6 +50,7 @@ def __init__(self, data_source, custom_age_ranges=None): self._numeric_cols = data_source.numeric_cols # Extract numeric columns from config self._columns = data_source.columns self.raw_data = None + self._date_column = data_source.date_column # Load preprocessing plugin if specified self.preprocessor = None @@ -221,6 +222,11 @@ def create_sheets_from_df(self, df: pd.DataFrame): Returns: None """ + if self._date_column not in df.columns: + self._date_column = df.columns[0] + df.rename(columns={self._date_column: 'date'}, inplace=True) # Ensure the date column is named 'date' + # make sure the date column is datetime + df['date'] = pd.to_datetime(df['date']) for col in self._columns: if col in df.columns: df_cumsum = self.calculate_cumulative_sums(df, col) @@ -228,19 +234,18 @@ def create_sheets_from_df(self, df: pd.DataFrame): labels = self._numeric_cols[col].labels if hasattr(self._numeric_cols[col], 'labels') else None if labels: # The first column (e.g., date) remains at index 0. - date_column = df_cumsum.columns[0] # Keep only labels that are present in the DataFrame. labels_in_df = [col for col in labels if col in df_cumsum.columns] # The remaining columns are those not in labels_in_df and not the date column. remaining_cols = [col for col in df_cumsum.columns if - col not in labels_in_df and col != date_column] + col not in labels_in_df and col != 'date'] # Build the new column order. - new_order = [date_column] + labels_in_df + remaining_cols + new_order = ['date'] + labels_in_df + remaining_cols df_cumsum = df_cumsum[new_order] self.sheets[col] = DataSheet(col, self.data_source, self.custom_age_ranges, is_excel=False, df=df_cumsum) - def calculate_cumulative_sums(self, df: pd.DataFrame, col: str): + def calculate_cumulative_sums(self, df: pd.DataFrame, col: str) -> pd.DataFrame: """ Calculates cumulative sums for a given column. diff --git a/src/midrc_react/core/jsdconfig.py b/src/midrc_react/core/jsdconfig.py index 5dba159..11f4af1 100644 --- a/src/midrc_react/core/jsdconfig.py +++ b/src/midrc_react/core/jsdconfig.py @@ -50,6 +50,7 @@ class DataSourceConfig(BaseModel): numeric_cols: Optional[Dict[str, NumericColumnConfig]] = None plugin: Optional[str] = None date: Optional[str] = None + date_column: Optional[str] = Field('date', alias='date column') remove_column_name_text: Optional[List[str]] = Field(None, alias='remove column name text') content: Optional[Any] = None # Placeholder for loaded content diff --git a/src/midrc_react/gui/common/jsdview_base.py b/src/midrc_react/gui/common/jsdview_base.py index a3154f2..7489fe4 100644 --- a/src/midrc_react/gui/common/jsdview_base.py +++ b/src/midrc_react/gui/common/jsdview_base.py @@ -23,6 +23,8 @@ from PySide6.QtCore import QObject, Signal from PySide6.QtWidgets import QMainWindow +from midrc_react.core.jsdconfig import DataSourceConfig + class FileInfo(BaseModel): description: Optional[str] = None @@ -146,7 +148,7 @@ class JsdViewBase(QObject): _data_selection_group_box = GroupBoxData() _controller = None update_view_on_controller_initialization = True - add_data_source = Signal(dict) + add_data_source = Signal(DataSourceConfig) def __init__(self): """ diff --git a/src/midrc_react/gui/pyside6/file_open_dialogs.py b/src/midrc_react/gui/pyside6/file_open_dialogs.py index 477115f..6045415 100644 --- a/src/midrc_react/gui/pyside6/file_open_dialogs.py +++ b/src/midrc_react/gui/pyside6/file_open_dialogs.py @@ -36,7 +36,7 @@ import yaml from midrc_react.gui.pyside6.columnselectordialog import ColumnSelectorDialog, NumericColumnSelectorDialog - +from midrc_react.core.jsdconfig import DataSourceConfig, NumericColumnConfig class BaseFileOptionsDialog(QDialog): """ @@ -162,8 +162,10 @@ def __init__(self, parent: Optional[QWidget], file_name: str, form_layout = QFormLayout() self.name_line_edit = QLineEdit() self.description_line_edit = QLineEdit() + self.date_column_line_edit = QLineEdit() form_layout.addRow("Name (Plot Titles):", self.name_line_edit) form_layout.addRow("Description (Drop-Down Menu):", self.description_line_edit) + form_layout.addRow("Date Column:", self.date_column_line_edit) top_layout.addLayout(form_layout) # Step 2: Plugin dropdown and Process Plugin button @@ -266,6 +268,7 @@ def _load_values_from_group(self, settings, group: str) -> Dict[str, Any]: "plugin": settings.value("plugin", ""), "name": settings.value("name", ""), "description": settings.value("description", ""), + "date_column": settings.value("date_column", "date"), "columns": settings.value("columns", ""), "numeric_cols": settings.value("numeric_cols", ""), } @@ -293,6 +296,7 @@ def load_defaults(self) -> None: saved_description = ( file_values.get("description") or last_values.get("description", self.default_base) ) + saved_date_column = file_values.get("date_column") or last_values.get("date_column", "date") saved_columns = file_values.get("columns") or last_values.get("columns", "") saved_numeric = file_values.get("numeric_cols") or last_values.get("numeric_cols", "") @@ -302,6 +306,7 @@ def load_defaults(self) -> None: self.plugin_combo.setCurrentIndex(idx) self.name_line_edit.setText(saved_name) self.description_line_edit.setText(saved_description) + self.date_column_line_edit.setText(saved_date_column) if saved_columns: self.columns_line_edit.setText(saved_columns) if saved_numeric: @@ -319,6 +324,7 @@ def _set_settings_values_to_group(self, settings, group): settings.setValue("plugin", self.plugin_combo.currentText().strip()) settings.setValue("name", self.name_line_edit.text()) settings.setValue("description", self.description_line_edit.text()) + settings.setValue("date_column", self.date_column_line_edit.text()) settings.setValue("columns", self.columns_line_edit.text()) settings.setValue("numeric_cols", self.numeric_cols_text_edit.toPlainText()) settings.endGroup() @@ -473,6 +479,7 @@ def get_data(self) -> Dict[str, Any]: return { "name": self.name_line_edit.text(), "description": self.description_line_edit.text(), + "date_column": self.date_column_line_edit.text(), "columns": selected_cols, "numeric_cols": numeric_cols, "plugin": self.plugin_combo.currentText().strip(), @@ -512,17 +519,13 @@ def open_excel_file_dialog(self: Any) -> None: if dialog.exec() != QDialog.Accepted: return # User cancelled the dialog - data_source_dict: Dict[str, Union[str, Any]] = { - 'name': dialog.name_line_edit.text(), - 'description': dialog.description_line_edit.text(), - 'data type': 'file', - 'filename': file_name, - 'remove column name text': dialog.remove_column_text_line_edit.text(), - } - self.add_data_source.emit(data_source_dict) + data_source_dict = dialog.get_data() + data_source_config = DataSourceConfig(**data_source_dict, filename=file_name, data_type='file') + self.add_data_source.emit(data_source_config) + self.dataselectiongroupbox.add_file_to_comboboxes( - data_source_dict['description'], - data_source_dict['name'], + data_source_config.description, + data_source_config.name, ) @@ -565,10 +568,11 @@ def open_yaml_input_dialog(self: Any) -> None: # Save the current YAML content as default settings.setValue("yaml_input/default_yaml", yaml_content) - self.add_data_source.emit(data_source_dict) + data_source_config = DataSourceConfig(**data_source_dict) + self.add_data_source.emit(data_source_config) self.dataselectiongroupbox.add_file_to_comboboxes( - data_source_dict.get("description", ""), - data_source_dict.get("name", ""), + data_source_config.description, + data_source_config.name, ) @@ -591,17 +595,10 @@ def open_csv_tsv_file_dialog(self: Any) -> None: return # User cancelled the dialog data = dialog.get_data() - data_source_dict: Dict[str, Any] = { - 'name': data['name'], - 'description': data['description'], - 'data type': 'file', - 'filename': file_name, - 'columns': data['columns'], - 'numeric_cols': data['numeric_cols'], # Caller can later parse this as YAML if desired - 'plugin': data['plugin'], - } - self.add_data_source.emit(data_source_dict) + data_source_config = DataSourceConfig(**data, filename=file_name, data_type='file') + + self.add_data_source.emit(data_source_config) self.dataselectiongroupbox.add_file_to_comboboxes( - data_source_dict.get('description', ''), - data_source_dict.get('name', ''), + data_source_config.description, + data_source_config.name, ) diff --git a/src/midrc_react/gui/pyside6/jsdview.py b/src/midrc_react/gui/pyside6/jsdview.py index f59e7aa..6a30da0 100644 --- a/src/midrc_react/gui/pyside6/jsdview.py +++ b/src/midrc_react/gui/pyside6/jsdview.py @@ -37,7 +37,7 @@ ) from midrc_react.core.datetimetools import convert_date_to_milliseconds, numpy_datetime64_to_qdate -from midrc_react.core.jsdconfig import DataSourceConfigList +from midrc_react.core.jsdconfig import DataSourceConfigList, DataSourceConfig from midrc_react.gui.common.jsdview_base import JsdViewBase from midrc_react.gui.pyside6.copyabletableview import CopyableTableView from midrc_react.gui.pyside6.dataselectiongroupbox import JsdDataSelectionGroupBox @@ -56,7 +56,7 @@ class JsdWindow(QMainWindow, JsdViewBase): WINDOW_TITLE (str): The window title. DOCK_TITLES (dict): Titles for various dock widgets. """ - add_data_source = Signal(dict) + add_data_source = Signal(DataSourceConfig) WINDOW_TITLE: str = 'MIDRC-REACT Representativeness Exploration and Comparison Tool' DOCK_TITLES: Dict[str, str] = {