Skip to content
Snippets Groups Projects
plot_window.py 9.63 KiB
Newer Older
  • Learn to ignore specific revisions
  • """PlotWindow is a window in which simulation results are plotted."""
    
    
    from typing import Dict, List, Mapping, Optional, Sequence  # , Union
    
    # from numpy import (array, real, imag, real_if_close, absolute, angle)
    import numpy as np
    
    from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
    
    from matplotlib.figure import Figure
    from matplotlib.ticker import MaxNLocator
    from qtpy.QtCore import Qt
    
    from qtpy.QtWidgets import (
    
        QApplication,
        QCheckBox,
        QHBoxLayout,
        QListWidget,
        QListWidgetItem,
        QPushButton,
    
        QSizePolicy,
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
    from b_asic.gui_utils.icons import get_icon
    
    from b_asic.operation import ResultKey
    from b_asic.types import Num
    
    
    class PlotWindow(QWidget):
    
        """
        Dialog for plotting the result of a simulation.
    
        Parameters
        ----------
        sim_result : dict
    
            Simulation results of the form obtained from
            :attr:`~b_asic.simulation.Simulation.results`.
    
        sfg_name : str, optional
            The name of the SFG.
        parent : optional
            The parent window.
        """
    
            sim_result: Mapping[ResultKey, Sequence[Num]],
    
            sfg_name: Optional[str] = None,
    
            super().__init__()
    
            self.setWindowFlags(
                Qt.WindowTitleHint
                | Qt.WindowCloseButtonHint
                | Qt.WindowMinimizeButtonHint
                | Qt.WindowMaximizeButtonHint
            )
    
            title = (
                f"Simulation results: {sfg_name}"
                if sfg_name is not None
                else "Simulation results"
            )
            self.setWindowTitle(title)
    
            ########### Categorizing/sorting/renaming sim_results: ##############
            #  take: sim_results
            #  generate: key_order, initially_checked
            #  generate: updated_result
            initially_checked = []
            dict_to_sort = {}  # use key=m+1/n, where m:3=input, 4=output, 2=T, 1=others
            updated_result = {}
            n = 0
            for key, result in sim_result.items():
                key2 = key  # in most cases
    
                if re.fullmatch("in[0-9]+", key):
    
                elif re.fullmatch("[0-9]+", key):
    
                elif re.fullmatch("t[0-9]+", key):
    
                    m = 2
                else:
                    m = 1
    
                if all(np.imag(np.real_if_close(result)) == 0):
                    n = n + 1
                    dict_to_sort[m + 1 / n] = key2
                    updated_result[key2] = np.real(result)
                    if m == 3:  # output
                        initially_checked.append(key2)
    
                    # The same again, but split into several lines
                    dict_to_sort[m + 1 / (n + 1)] = key2 + '_re'
                    dict_to_sort[m + 1 / (n + 2)] = key2 + '_im'
                    dict_to_sort[m + 1 / (n + 3)] = key2 + '_mag'
                    dict_to_sort[m + 1 / (n + 4)] = key2 + '_ang'
                    updated_result[key2 + '_re'] = np.real(result)
                    updated_result[key2 + '_im'] = np.imag(result)
                    updated_result[key2 + '_mag'] = np.absolute(result)
                    updated_result[key2 + '_ang'] = np.angle(result)
                    n = n + 4
                    if m == 3:  # output
                        initially_checked.append(key2 + '_re')
                        initially_checked.append(key2 + '_im')
    
            key_order = list(dict(sorted(dict_to_sort.items(), reverse=True)).values())
    
    
            # Layout: ############################################
    
            self._dialog_layout = QHBoxLayout()
            self.setLayout(self._dialog_layout)
    
            self._dialog_layout.addLayout(listlayout)
            self._dialog_layout.addLayout(plotlayout)
    
    
            ########### Plot: ##############
            # Do this before the list layout, as the list layout will re/set visibility
            # Note: The order is of importens. Interesting lines last, to be on top.
    
            # self.plotcanvas = PlotCanvas(
            #    logger=logger, parent=self, width=5, height=4, dpi=100
            # )
    
            self._plot_fig = Figure(figsize=(5, 4), layout="compressed")
    
            self._plot_axes = self._plot_fig.add_subplot(111)
            self._plot_axes.xaxis.set_major_locator(MaxNLocator(integer=True))
    
            for key in key_order:
                line = self._plot_axes.plot(updated_result[key], label=key)
    
                self._lines[key] = line[0]
    
    
            self._plot_canvas = FigureCanvas(self._plot_fig)
    
            self._toolbar = NavigationToolbar(self._plot_canvas, self)
    
            plotlayout.addWidget(self._toolbar)
    
            plotlayout.addWidget(self._plot_canvas)
    
            ########### List layout: ##############
    
    
            # Add two buttons for selecting all/none:
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            self._button_all = QPushButton(get_icon('all'), "&All")
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            self._button_all.clicked.connect(self._button_all_click)
            hlayout.addWidget(self._button_all)
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            self._button_none = QPushButton(get_icon('none'), "&None")
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            self._button_none.clicked.connect(self._button_none_click)
            hlayout.addWidget(self._button_none)
    
            listlayout.addLayout(hlayout)
    
            # Add the entire list
    
            self._checklist = QListWidget()
            self._checklist.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
            self._checklist.itemChanged.connect(self._item_change)
    
            for key in key_order:
    
                list_item = QListWidgetItem(key)
                listitems[key] = list_item
                self._checklist.addItem(list_item)
    
                list_item.setCheckState(Qt.CheckState.Unchecked)
    
            for key in initially_checked:
    
                listitems[key].setCheckState(Qt.CheckState.Checked)
    
            # self._checklist.setFixedWidth(150)
            listlayout.addWidget(self._checklist)
    
            self._legend = self._plot_axes.legend()
    
            self._legend_checkbox = QCheckBox("&Legend")
            self._legend_checkbox.stateChanged.connect(self._legend_checkbox_change)
            self._legend_checkbox.setCheckState(Qt.CheckState.Checked)
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            self._legend_checkbox.setIcon(get_icon('legend'))
    
            listlayout.addWidget(self._legend_checkbox)
    
            # self.ontop_checkbox = QCheckBox("&On top")
            # self.ontop_checkbox.stateChanged.connect(self._ontop_checkbox_change)
            # self.ontop_checkbox.setCheckState(Qt.CheckState.Unchecked)
            # listlayout.addWidget(self.ontop_checkbox)
    
            relim_button = QPushButton("&Recompute limits")
            relim_button.clicked.connect(self._relim)
            listlayout.addWidget(relim_button)
    
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
            button_close = QPushButton(get_icon('close'), "&Close", self)
    
            button_close.clicked.connect(self.close)
            listlayout.addWidget(button_close)
    
    
            # Done. Tell the functions below to redraw the canvas when needed.
            # self.plotcanvas.draw()
            self._auto_redraw = True
    
    
        def _legend_checkbox_change(self, check_state):
            self._legend.set(visible=(check_state == Qt.CheckState.Checked))
    
                if check_state == Qt.CheckState.Checked:
    
                    self._legend = self._plot_axes.legend()
                self._plot_canvas.draw()
    
        def _button_all_click(self, event):
            self._auto_redraw = False
    
            for x in range(self._checklist.count()):
                self._checklist.item(x).setCheckState(Qt.CheckState.Checked)
    
            self._update_legend()
    
        def _update_legend(self):
            self._legend = self._plot_axes.legend()
    
            self._plot_canvas.draw()
    
        def _button_none_click(self, event):
            self._auto_redraw = False
    
            for x in range(self._checklist.count()):
                self._checklist.item(x).setCheckState(Qt.CheckState.Unchecked)
    
            self._update_legend()
    
        def _item_change(self, listitem):
    
            if listitem.checkState() == Qt.CheckState.Checked:
                self._plot_axes.add_line(self._lines[key])
            else:
                self._lines[key].remove()
    
                self._update_legend()
    
        def _relim(self, event=None):
            self._plot_axes.relim(True)
            self._plot_axes.autoscale(True)
            self._plot_axes.autoscale(axis='x', tight=True)
            self._plot_axes.autoscale(axis='y')
            self._plot_canvas.draw()
    
    
    def start_simulation_dialog(
        sim_results: Dict[str, List[complex]], sfg_name: Optional[str] = None
    ):
    
        """
        Display the simulation results window.
    
        Parameters
        ----------
        sim_results : dict
    
            Simulation results of the form obtained from
            :attr:`~b_asic.simulation.Simulation.results`.
    
        sfg_name : str, optional
    
            The name of the SFG.
    
        if not QApplication.instance():
    
            # QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    
            app = QApplication(sys.argv)
        else:
            app = QApplication.instance()
    
        win = PlotWindow(sim_result=sim_results, sfg_name=sfg_name)
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
        win.show()
    
    # Simple test of the dialog
    if __name__ == "__main__":
        sim_res = {
    
            '0': [0.5, 0.6, 0.5, 0],
            '1': [0.0, 1.0 + 0.3j, 0.5, 0.1j],
    
            'add1': [0.5, 0.5, 0, 0],
            'cmul1': [0, 0.5, 0, 0],
            'cmul2': [0.5, 0, 0, 0],
            'in1': [1, 0, 0, 0],
    
            'in2': [0.1, 2, 0, 0],
    
            't2': [0, 0, 1, 0],
            't3': [0, 0, 0, 1],
    
        start_simulation_dialog(sim_res, "Test data")