"""PlotWindow is a window in which simulation results are plotted.""" import re import sys 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_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg 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, QVBoxLayout, QWidget, ) 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. """ def __init__( self, 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) self._auto_redraw = False ########### 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): m = 4 elif re.fullmatch("[0-9]+", key): m = 3 key2 = 'o' + 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) else: # 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: ############################################ # | list | icons | # | ... | plot | # | misc | | self._dialog_layout = QHBoxLayout() self.setLayout(self._dialog_layout) listlayout = QVBoxLayout() plotlayout = QVBoxLayout() 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)) self._lines = {} 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: hlayout = QHBoxLayout() self._button_all = QPushButton(get_icon('all'), "&All") self._button_all.clicked.connect(self._button_all_click) hlayout.addWidget(self._button_all) self._button_none = QPushButton(get_icon('none'), "&None") 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) listitems = {} 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) # Add additional checkboxes 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) 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) # Add "Close" buttons button_close = QPushButton(get_icon('close'), "&Close", self) button_close.clicked.connect(self.close) listlayout.addWidget(button_close) self._relim() # 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 self._auto_redraw: 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._auto_redraw = True 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._auto_redraw = True self._update_legend() def _item_change(self, listitem): key = listitem.text() if listitem.checkState() == Qt.CheckState.Checked: self._plot_axes.add_line(self._lines[key]) else: self._lines[key].remove() if self._auto_redraw: 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) win.show() app.exec_() # 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], 't1': [0, 1, 0, 0], 't2': [0, 0, 1, 0], 't3': [0, 0, 0, 1], } start_simulation_dialog(sim_res, "Test data")