Module saePisan.view.components.exploration.SummaryDataDialog

Classes

class SummaryDataDialog (parent)
Expand source code
class SummaryDataDialog(QDialog):
    """
    A dialog for summarizing data from two models and generating an R script.
    
    Attributes:
        parent (QWidget): The parent widget.
        model1 (Any): The first data model.
        model2 (Any): The second data model.
        all_columns_model1 (list): List of all columns in model1.
        all_columns_model2 (list): List of all columns in model2.
        selected_status (dict): Dictionary to store the status of selected variables.
        data_editor_label (QLabel): Label for the data editor list.
        data_editor_model (QStringListModel): Model for the data editor list.
        data_editor_list (DragDropListView): List view for the data editor.
        data_output_label (QLabel): Label for the data output list.
        data_output_model (QStringListModel): Model for the data output list.
        data_output_list (DragDropListView): List view for the data output.
        add_button (QPushButton): Button to add variables to the selected list.
        remove_button (QPushButton): Button to remove variables from the selected list.
        selected_label (QLabel): Label for the selected variables list.
        selected_model (QStringListModel): Model for the selected variables list.
        selected_list (DragDropListView): List view for the selected variables.
        script_label (QLabel): Label for the R script.
        icon_label (QLabel): Label for the running icon.
        script_box (QTextEdit): Text box to display the generated R script.
        run_button (QPushButton): Button to run the R script.
    Methods:
        __init__(self, parent): Initializes the dialog with the given parent.
        set_model(self, model1, model2): Sets the data models and updates the lists.
        get_column_with_dtype(self, model): Returns a list of columns with their data types.
        add_variable(self): Adds selected variables to the selected list and generates the R script.
        remove_variable(self): Removes selected variables from the selected list and generates the R script.
        get_selected_columns(self): Returns a list of selected columns without data types.
        generate_r_script(self): Generates the R script based on selected variables.
        accept(self): Runs the R script and handles the result.
        closeEvent(self, event): Resets the dialog when it is closed.
        handle_drop(self, target_widget, items): Handles the drop event for the list views.
        toggle_r_script_visibility(self): Toggles the visibility of the R script text edit area.
    """
    
    def __init__(self, parent):
        super().__init__(parent) 
        self.parent = parent
        self.model1 = None
        self.model2 = None
        
        self.all_columns_model1 = []
        self.all_columns_model2 = []

        self.setWindowTitle("Summary Data")

        # Store selected variable status
        self.selected_status = {}

        # Main layout
        self.main_layout = QVBoxLayout(self)
        content_layout = QHBoxLayout()

        # Left layout for Data Editor and Data Output
        left_layout = QVBoxLayout()

        self.data_editor_label = QLabel("Data Editor", self)
        self.data_editor_model = QStringListModel()
        self.data_editor_list = DragDropListView(parent=self)
        self.data_editor_list.setModel(self.data_editor_model)
        self.data_editor_list.setSelectionMode(QListView.SelectionMode.MultiSelection)
        self.data_editor_list.setEditTriggers(QListView.EditTrigger.NoEditTriggers)
        left_layout.addWidget(self.data_editor_label)
        left_layout.addWidget(self.data_editor_list)

        self.data_output_label = QLabel("Data Output", self)
        self.data_output_model = QStringListModel()
        self.data_output_list = DragDropListView(parent=self)
        self.data_output_list.setModel(self.data_output_model)
        self.data_output_list.setSelectionMode(QListView.SelectionMode.MultiSelection)
        self.data_output_list.setEditTriggers(QListView.EditTrigger.NoEditTriggers)
        left_layout.addWidget(self.data_output_label)
        left_layout.addWidget(self.data_output_list)

        content_layout.addLayout(left_layout)

        # Middle layout for buttons
        button_layout = QVBoxLayout()
        self.add_button = QPushButton("🡆", self)
        self.add_button.clicked.connect(self.add_variable)
        self.add_button.setFixedSize(50, 35) 
        self.add_button.setStyleSheet("font-size: 24px;")  
        self.remove_button = QPushButton("🡄", self)
        self.remove_button.clicked.connect(self.remove_variable)
        self.remove_button.setFixedSize(50, 35)
        self.remove_button.setStyleSheet("font-size: 24px;") 
        button_layout.addStretch()
        button_layout.addWidget(self.add_button)
        button_layout.addWidget(self.remove_button)
        button_layout.addStretch()
        content_layout.addLayout(button_layout)

        # Right layout
        right_layout = QVBoxLayout()
        self.selected_label = QLabel("Variable", self)
        self.selected_model = QStringListModel()
        self.selected_list = DragDropListView(parent=self)
        self.selected_list.setModel(self.selected_model)
        self.selected_list.setSelectionMode(QListView.SelectionMode.MultiSelection)
        right_layout.addWidget(self.selected_label)
        right_layout.addWidget(self.selected_list)

        content_layout.addLayout(right_layout)
        self.main_layout.addLayout(content_layout)

        # Horizontal layout for label and icon
        self.script_label = QLabel("R Script:", self)
        self.icon_label = QLabel()
        self.icon_label.setPixmap(QIcon("assets/running.svg").pixmap(QSize(16, 30)))
        self.icon_label.setFixedSize(16, 30)
        self.icon_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop)
        
        self.toggle_script_button = QToolButton()
        self.toggle_script_button.setIcon(QIcon("assets/more.svg"))
        self.toggle_script_button.setIconSize(QSize(16, 16))
        self.toggle_script_button.setCheckable(True)
        self.toggle_script_button.setChecked(False)
        self.toggle_script_button.clicked.connect(self.toggle_r_script_visibility)

        self.button_layout = QHBoxLayout()
        self.button_layout.addWidget(self.script_label)
        self.button_layout.addWidget(self.toggle_script_button)
        self.button_layout.setAlignment(self.script_label, Qt.AlignmentFlag.AlignLeft)
        self.button_layout.setAlignment(self.toggle_script_button, Qt.AlignmentFlag.AlignLeft)

        self.script_layout = QHBoxLayout()
        self.script_layout.addLayout(self.button_layout)
        self.script_layout.addStretch()
        self.script_layout.addWidget(self.icon_label)
        self.icon_label.setVisible(False)
        self.script_layout.setAlignment(self.script_label, Qt.AlignmentFlag.AlignLeft)

        self.main_layout.addLayout(self.script_layout)

        self.script_box = QTextEdit()
        self.script_box.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
        self.script_box.setReadOnly(False)
        self.script_box.setVisible(False)
        self.main_layout.addWidget(self.script_box)

        # Run button
        button_row_layout = QHBoxLayout()
        self.run_button = QPushButton("Run", self)
        self.run_button.clicked.connect(self.accept)
        button_row_layout.addWidget(self.run_button, alignment=Qt.AlignmentFlag.AlignRight)
        self.main_layout.addLayout(button_row_layout)

        # Selection mode for list view
        self.data_editor_list.setSelectionMode(QListView.SelectionMode.ExtendedSelection)
        self.data_output_list.setSelectionMode(QListView.SelectionMode.ExtendedSelection)
        self.selected_list.setSelectionMode(QListView.SelectionMode.ExtendedSelection)

    def handle_drop(self, target_widget, items):
        # Mapping widget to model
        widget_model_map = {
            self.data_editor_list: (self.data_editor_model, self.all_columns_model1),
            self.data_output_list: (self.data_output_model, self.all_columns_model2),
            self.selected_list: (self.selected_model, None),
        }

        if target_widget not in widget_model_map:
            return

        target_model, allowed_columns = widget_model_map[target_widget]
        current_items = target_model.stringList()

        filtered_items = []
        contains_none = False

        for item in items:
            if "[None]" in item:
                contains_none = True
                continue

            if target_widget == self.selected_list:
                filtered_items.append(item)
            else:
                column_name = item.split(" ")[0]
                if allowed_columns and column_name in [col.split(" ")[0] for col in allowed_columns]:
                    filtered_items.append(item)

        # ❗️Tampilkan peringatan jika ada item [None]
        if contains_none:
            QMessageBox.warning(self, "Warning", "Cannot drop variables with type None.")

        # Tambahkan item yang lolos ke model target
        for item in filtered_items:
            if item not in current_items:
                current_items.append(item)

        # Hapus dari model lain
        for other_widget, (model, _) in widget_model_map.items():
            if model == target_model:
                continue
            other_items = model.stringList()
            for item in filtered_items:
                if item in other_items:
                    other_items.remove(item)
            model.setStringList(other_items)

        # Atur ulang urutan
        if target_widget == self.data_editor_list:
            ordered = [col for col in self.all_columns_model1 if col in current_items]
            target_model.setStringList(ordered)
        elif target_widget == self.data_output_list:
            ordered = [col for col in self.all_columns_model2 if col in current_items]
            target_model.setStringList(ordered)
        else:
            target_model.setStringList(current_items)

        self.generate_r_script()


    def toggle_r_script_visibility(self):
        """
        Toggles the visibility of the R script text edit area and updates the toggle button icon.
        """
        is_visible = self.script_box.isVisible()
        self.script_box.setVisible(not is_visible)
        if not is_visible:
            self.toggle_script_button.setIcon(QIcon("assets/less.svg"))
        else:
            self.toggle_script_button.setIcon(QIcon("assets/more.svg"))
    
    def set_model(self, model1, model2):
        """
        Sets the data models and updates the lists in the dialog.
        
        Args:
            model1 (Any): The first data model.
            model2 (Any): The second data model.
        """
        self.model1 = model1
        self.model2 = model2
        self.data_editor_model.setStringList(self.get_column_with_dtype(model1))
        self.data_output_model.setStringList(self.get_column_with_dtype(model2))
        self.all_columns_model1 = self.get_column_with_dtype(model1)
        self.all_columns_model2 = self.get_column_with_dtype(model2)

    def get_column_with_dtype(self, model):
        """
        Returns a list of columns with simplified data types:
        String, Numeric, or None.
        """
        self.columns = []
        for col, dtype in zip(model.get_data().columns, model.get_data().dtypes):
            if dtype == pl.Utf8:
                tipe = "String"
            elif dtype == pl.Null:
                tipe = "None"
            else:
                tipe = "Numeric"
            self.columns.append(f"{col} [{tipe}]")
        return self.columns

    def add_variable(self):
        """
        Adds selected variables to the selected list and generates the R script.
        Variables of type None are not allowed.
        """
        selected_indexes = self.data_editor_list.selectedIndexes() + self.data_output_list.selectedIndexes()
        selected_items = [index.data() for index in selected_indexes]

        contains_none = any("[None]" in item for item in selected_items)
        if contains_none:
            QMessageBox.warning(self, "Warning", "Selected variables must not be of type None.")
            return

        selected_list = self.selected_model.stringList()

        for item in selected_items:
            if item in self.data_editor_model.stringList():
                editor_list = self.data_editor_model.stringList()
                editor_list.remove(item)
                self.data_editor_model.setStringList(editor_list)
            elif item in self.data_output_model.stringList():
                output_list = self.data_output_model.stringList()
                output_list.remove(item)
                self.data_output_model.setStringList(output_list)

            if item not in selected_list:
                selected_list.append(item)

        self.selected_model.setStringList(selected_list)
        self.generate_r_script()

    def remove_variable(self):
        """
        Removes selected variables from the selected list and generates the R script.
        """
        selected_indexes = self.selected_list.selectedIndexes()
        selected_items = [index.data() for index in selected_indexes]

        selected_list = self.selected_model.stringList()
        editor_list = self.data_editor_model.stringList()
        output_list = self.data_output_model.stringList()

        for item in selected_items:
            column_name = item.split(" ")[0]

            # Check if the item belongs to model1 (editor) or model2 (output)
            if column_name in [col.split(" ")[0] for col in self.all_columns_model1]:
                if item not in editor_list:
                    editor_list.append(item)
                    # Re-sort according to all_columns_model1
                    editor_list = [col for col in self.all_columns_model1 if col in editor_list]

            elif column_name in [col.split(" ")[0] for col in self.all_columns_model2]:
                if item not in output_list:
                    output_list.append(item)
                    # Re-sort according to all_columns_model2
                    output_list = [col for col in self.all_columns_model2 if col in output_list]

            # Remove from selected
            if item in selected_list:
                selected_list.remove(item)

        self.selected_model.setStringList(selected_list)
        self.data_editor_model.setStringList(editor_list)
        self.data_output_model.setStringList(output_list)
        self.generate_r_script()

    def get_selected_columns(self):
        """
        Returns a list of selected columns without data types.
        
        Returns:
            list: A list of selected column names.
        """
        return [item.rsplit(" [String]", 1)[0].rsplit(" [Numeric]", 1)[0] for item in self.selected_model.stringList()]

    def generate_r_script(self):
        """
        Generates the R script based on selected variables and updates the script box.
        """
        selected_columns = self.get_selected_columns()
        if not selected_columns:
            self.script_box.setPlainText("")
            return
        formatted_columns = ', '.join(f'"{col}"' for col in selected_columns)
        script = f"summary_results <- summary(data[, c({formatted_columns})])"
        self.script_box.setPlainText(script)

    def accept(self):
        """
        Runs the R script and handles the result, displaying messages based on success or failure.
        """
        r_script = self.script_box.toPlainText()
        if not r_script:
            QMessageBox.warning(self, "Empty Script", "Please generate a script before running.")
            return
        self.run_button.setEnabled(False)
        self.run_button.setText("Running...")
        self.icon_label.setVisible(True)
        summary_data = SummaryData(self.model1, self.model2)

        controller = SummaryDataController(summary_data)
        controller.run_model(r_script)

        if not summary_data.error:
            QMessageBox.information(self, "Summary Data", "Exploration has been completed.")
        else:
            QMessageBox.critical(self, "Summary Data", summary_data.result)
            
        # self.parent.add_output(r_script, summary_data.result)
        print(r_script)
        display_script_and_output(self.parent, r_script, summary_data.result)
        self.parent.tab_widget.setCurrentWidget(self.parent.output_tab)
        self.icon_label.setVisible(False)
        self.run_button.setText("Run")
        self.run_button.setEnabled(True)
        self.close()

    def closeEvent(self, event):
        """
        Resets the dialog when it is closed.
        
        Args:
            event (QCloseEvent): The close event.
        """
        self.selected_model.setStringList([])
        self.script_box.setPlainText("")
        event.accept()

A dialog for summarizing data from two models and generating an R script.

Attributes

parent : QWidget
The parent widget.
model1 : Any
The first data model.
model2 : Any
The second data model.
all_columns_model1 : list
List of all columns in model1.
all_columns_model2 : list
List of all columns in model2.
selected_status : dict
Dictionary to store the status of selected variables.
data_editor_label : QLabel
Label for the data editor list.
data_editor_model : QStringListModel
Model for the data editor list.
data_editor_list : DragDropListView
List view for the data editor.
data_output_label : QLabel
Label for the data output list.
data_output_model : QStringListModel
Model for the data output list.
data_output_list : DragDropListView
List view for the data output.
add_button : QPushButton
Button to add variables to the selected list.
remove_button : QPushButton
Button to remove variables from the selected list.
selected_label : QLabel
Label for the selected variables list.
selected_model : QStringListModel
Model for the selected variables list.
selected_list : DragDropListView
List view for the selected variables.
script_label : QLabel
Label for the R script.
icon_label : QLabel
Label for the running icon.
script_box : QTextEdit
Text box to display the generated R script.
run_button : QPushButton
Button to run the R script.

Methods

init(self, parent): Initializes the dialog with the given parent. set_model(self, model1, model2): Sets the data models and updates the lists. get_column_with_dtype(self, model): Returns a list of columns with their data types. add_variable(self): Adds selected variables to the selected list and generates the R script. remove_variable(self): Removes selected variables from the selected list and generates the R script. get_selected_columns(self): Returns a list of selected columns without data types. generate_r_script(self): Generates the R script based on selected variables. accept(self): Runs the R script and handles the result. closeEvent(self, event): Resets the dialog when it is closed. handle_drop(self, target_widget, items): Handles the drop event for the list views. toggle_r_script_visibility(self): Toggles the visibility of the R script text edit area.

Ancestors

  • PyQt6.QtWidgets.QDialog
  • PyQt6.QtWidgets.QWidget
  • PyQt6.QtCore.QObject
  • PyQt6.sip.wrapper
  • PyQt6.QtGui.QPaintDevice
  • PyQt6.sip.simplewrapper

Methods

def accept(self)
Expand source code
def accept(self):
    """
    Runs the R script and handles the result, displaying messages based on success or failure.
    """
    r_script = self.script_box.toPlainText()
    if not r_script:
        QMessageBox.warning(self, "Empty Script", "Please generate a script before running.")
        return
    self.run_button.setEnabled(False)
    self.run_button.setText("Running...")
    self.icon_label.setVisible(True)
    summary_data = SummaryData(self.model1, self.model2)

    controller = SummaryDataController(summary_data)
    controller.run_model(r_script)

    if not summary_data.error:
        QMessageBox.information(self, "Summary Data", "Exploration has been completed.")
    else:
        QMessageBox.critical(self, "Summary Data", summary_data.result)
        
    # self.parent.add_output(r_script, summary_data.result)
    print(r_script)
    display_script_and_output(self.parent, r_script, summary_data.result)
    self.parent.tab_widget.setCurrentWidget(self.parent.output_tab)
    self.icon_label.setVisible(False)
    self.run_button.setText("Run")
    self.run_button.setEnabled(True)
    self.close()

Runs the R script and handles the result, displaying messages based on success or failure.

def add_variable(self)
Expand source code
def add_variable(self):
    """
    Adds selected variables to the selected list and generates the R script.
    Variables of type None are not allowed.
    """
    selected_indexes = self.data_editor_list.selectedIndexes() + self.data_output_list.selectedIndexes()
    selected_items = [index.data() for index in selected_indexes]

    contains_none = any("[None]" in item for item in selected_items)
    if contains_none:
        QMessageBox.warning(self, "Warning", "Selected variables must not be of type None.")
        return

    selected_list = self.selected_model.stringList()

    for item in selected_items:
        if item in self.data_editor_model.stringList():
            editor_list = self.data_editor_model.stringList()
            editor_list.remove(item)
            self.data_editor_model.setStringList(editor_list)
        elif item in self.data_output_model.stringList():
            output_list = self.data_output_model.stringList()
            output_list.remove(item)
            self.data_output_model.setStringList(output_list)

        if item not in selected_list:
            selected_list.append(item)

    self.selected_model.setStringList(selected_list)
    self.generate_r_script()

Adds selected variables to the selected list and generates the R script. Variables of type None are not allowed.

def closeEvent(self, event)
Expand source code
def closeEvent(self, event):
    """
    Resets the dialog when it is closed.
    
    Args:
        event (QCloseEvent): The close event.
    """
    self.selected_model.setStringList([])
    self.script_box.setPlainText("")
    event.accept()

Resets the dialog when it is closed.

Args

event : QCloseEvent
The close event.
def generate_r_script(self)
Expand source code
def generate_r_script(self):
    """
    Generates the R script based on selected variables and updates the script box.
    """
    selected_columns = self.get_selected_columns()
    if not selected_columns:
        self.script_box.setPlainText("")
        return
    formatted_columns = ', '.join(f'"{col}"' for col in selected_columns)
    script = f"summary_results <- summary(data[, c({formatted_columns})])"
    self.script_box.setPlainText(script)

Generates the R script based on selected variables and updates the script box.

def get_column_with_dtype(self, model)
Expand source code
def get_column_with_dtype(self, model):
    """
    Returns a list of columns with simplified data types:
    String, Numeric, or None.
    """
    self.columns = []
    for col, dtype in zip(model.get_data().columns, model.get_data().dtypes):
        if dtype == pl.Utf8:
            tipe = "String"
        elif dtype == pl.Null:
            tipe = "None"
        else:
            tipe = "Numeric"
        self.columns.append(f"{col} [{tipe}]")
    return self.columns

Returns a list of columns with simplified data types: String, Numeric, or None.

def get_selected_columns(self)
Expand source code
def get_selected_columns(self):
    """
    Returns a list of selected columns without data types.
    
    Returns:
        list: A list of selected column names.
    """
    return [item.rsplit(" [String]", 1)[0].rsplit(" [Numeric]", 1)[0] for item in self.selected_model.stringList()]

Returns a list of selected columns without data types.

Returns

list
A list of selected column names.
def handle_drop(self, target_widget, items)
Expand source code
def handle_drop(self, target_widget, items):
    # Mapping widget to model
    widget_model_map = {
        self.data_editor_list: (self.data_editor_model, self.all_columns_model1),
        self.data_output_list: (self.data_output_model, self.all_columns_model2),
        self.selected_list: (self.selected_model, None),
    }

    if target_widget not in widget_model_map:
        return

    target_model, allowed_columns = widget_model_map[target_widget]
    current_items = target_model.stringList()

    filtered_items = []
    contains_none = False

    for item in items:
        if "[None]" in item:
            contains_none = True
            continue

        if target_widget == self.selected_list:
            filtered_items.append(item)
        else:
            column_name = item.split(" ")[0]
            if allowed_columns and column_name in [col.split(" ")[0] for col in allowed_columns]:
                filtered_items.append(item)

    # ❗️Tampilkan peringatan jika ada item [None]
    if contains_none:
        QMessageBox.warning(self, "Warning", "Cannot drop variables with type None.")

    # Tambahkan item yang lolos ke model target
    for item in filtered_items:
        if item not in current_items:
            current_items.append(item)

    # Hapus dari model lain
    for other_widget, (model, _) in widget_model_map.items():
        if model == target_model:
            continue
        other_items = model.stringList()
        for item in filtered_items:
            if item in other_items:
                other_items.remove(item)
        model.setStringList(other_items)

    # Atur ulang urutan
    if target_widget == self.data_editor_list:
        ordered = [col for col in self.all_columns_model1 if col in current_items]
        target_model.setStringList(ordered)
    elif target_widget == self.data_output_list:
        ordered = [col for col in self.all_columns_model2 if col in current_items]
        target_model.setStringList(ordered)
    else:
        target_model.setStringList(current_items)

    self.generate_r_script()
def remove_variable(self)
Expand source code
def remove_variable(self):
    """
    Removes selected variables from the selected list and generates the R script.
    """
    selected_indexes = self.selected_list.selectedIndexes()
    selected_items = [index.data() for index in selected_indexes]

    selected_list = self.selected_model.stringList()
    editor_list = self.data_editor_model.stringList()
    output_list = self.data_output_model.stringList()

    for item in selected_items:
        column_name = item.split(" ")[0]

        # Check if the item belongs to model1 (editor) or model2 (output)
        if column_name in [col.split(" ")[0] for col in self.all_columns_model1]:
            if item not in editor_list:
                editor_list.append(item)
                # Re-sort according to all_columns_model1
                editor_list = [col for col in self.all_columns_model1 if col in editor_list]

        elif column_name in [col.split(" ")[0] for col in self.all_columns_model2]:
            if item not in output_list:
                output_list.append(item)
                # Re-sort according to all_columns_model2
                output_list = [col for col in self.all_columns_model2 if col in output_list]

        # Remove from selected
        if item in selected_list:
            selected_list.remove(item)

    self.selected_model.setStringList(selected_list)
    self.data_editor_model.setStringList(editor_list)
    self.data_output_model.setStringList(output_list)
    self.generate_r_script()

Removes selected variables from the selected list and generates the R script.

def set_model(self, model1, model2)
Expand source code
def set_model(self, model1, model2):
    """
    Sets the data models and updates the lists in the dialog.
    
    Args:
        model1 (Any): The first data model.
        model2 (Any): The second data model.
    """
    self.model1 = model1
    self.model2 = model2
    self.data_editor_model.setStringList(self.get_column_with_dtype(model1))
    self.data_output_model.setStringList(self.get_column_with_dtype(model2))
    self.all_columns_model1 = self.get_column_with_dtype(model1)
    self.all_columns_model2 = self.get_column_with_dtype(model2)

Sets the data models and updates the lists in the dialog.

Args

model1 : Any
The first data model.
model2 : Any
The second data model.
def toggle_r_script_visibility(self)
Expand source code
def toggle_r_script_visibility(self):
    """
    Toggles the visibility of the R script text edit area and updates the toggle button icon.
    """
    is_visible = self.script_box.isVisible()
    self.script_box.setVisible(not is_visible)
    if not is_visible:
        self.toggle_script_button.setIcon(QIcon("assets/less.svg"))
    else:
        self.toggle_script_button.setIcon(QIcon("assets/more.svg"))

Toggles the visibility of the R script text edit area and updates the toggle button icon.