From 069abe95d9788ecbfd841c3f2450b020d7770d36 Mon Sep 17 00:00:00 2001 From: Petter <petter.kallstrom@liu.se> Date: Thu, 16 Feb 2023 09:05:29 +0100 Subject: [PATCH] version 0.1 of updated plot window. Still, buggy legend --- b_asic/GUI/plot_window.py | 267 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 b_asic/GUI/plot_window.py diff --git a/b_asic/GUI/plot_window.py b/b_asic/GUI/plot_window.py new file mode 100644 index 00000000..b7b93e3b --- /dev/null +++ b/b_asic/GUI/plot_window.py @@ -0,0 +1,267 @@ +# TODO's: +# * Solve the legend update. That isn't working at all. +# * Change labels "All" and "None" into buttons. +# * Make it work with the main_window (probably requires a rebase or merge) + +import re +import sys + +from matplotlib.backends.backend_qt5agg import ( + FigureCanvasQTAgg as FigureCanvas, +) +from matplotlib.figure import Figure +from matplotlib.ticker import MaxNLocator +from qtpy.QtCore import Qt +from qtpy.QtGui import QKeySequence + +# Intereme imports for the Plot class: +from qtpy.QtWidgets import ( # QFrame,; QScrollArea,; QLineEdit, + QApplication, + QCheckBox, + QDialog, + QFileDialog, + QHBoxLayout, + QLabel, + QListWidget, + QListWidgetItem, + QPushButton, + QShortcut, + QSizePolicy, + QVBoxLayout, +) + + +class PlotCanvas(FigureCanvas): + def __init__(self, logger, parent=None, width=5, height=4, dpi=100): + fig = Figure(figsize=(width, height), dpi=dpi) + super().__init__(fig) + self.axes = fig.add_subplot(111) + self.axes.xaxis.set_major_locator(MaxNLocator(integer=True)) + self.legend = self.axes.legend() + self.logger = logger + + FigureCanvas.updateGeometry(self) + self.save_figure = QShortcut(QKeySequence("Ctrl+S"), self) + self.save_figure.activated.connect(self._save_plot_figure) + + def _save_plot_figure(self): + self.logger.info(f"Saving plot of figure: {self.sfg.name}.") + file_choices = "PNG (*.png)|*.png" + path, ext = QFileDialog.getSaveFileName( + self, "Save file", "", file_choices + ) + path = path.encode("utf-8") + if not path[-4:] == file_choices[-4:].encode("utf-8"): + path += file_choices[-4:].encode("utf-8") + + if path: + self.print_figure(path.decode(), dpi=self.dpi) + self.logger.info(f"Saved plot: {self.sfg.name} to path: {path}.") + + +class PlotWindow(QDialog): + def __init__( + self, + window, + sfg_name, + sim_result, + logger, + parent=None, + width=5, + height=4, + dpi=100, + ): + super().__init__() + self._window = window + self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowCloseButtonHint) + self.setWindowTitle("Simulation result") + self.sim_result = sim_result + self._auto_redraw = False + + # Categorise sim_results into inputs, outputs, delays, others + sim_res_ins = {} + sim_res_outs = {} + sim_res_delays = {} + sim_res_others = {} + + for key in sim_result: + if re.fullmatch("in[0-9]+", key): + sim_res_ins[key] = sim_result[key] + elif re.fullmatch("[0-9]+", key): + sim_res_outs[key] = sim_result[key] + elif re.fullmatch("t[0-9]+", key): + sim_res_delays[key] = sim_result[key] + else: + sim_res_others[key] = sim_result[key] + + # Layout: ############################################ + # | list | | + # | ... | plot | + # | misc | | + + self.dialog_layout = QHBoxLayout() + self.setLayout(self.dialog_layout) + + listlayout = QVBoxLayout() + self.plotcanvas = PlotCanvas( + logger=logger, parent=self, width=5, height=4, dpi=100 + ) + + self.dialog_layout.addLayout(listlayout) + self.dialog_layout.addWidget(self.plotcanvas) + + ########### 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._lines = {} + for key in ( + sim_res_others | sim_res_delays | sim_res_ins | sim_res_outs + ): + line = self.plotcanvas.axes.plot( + sim_result[key], visible=False, label=key + ) + self._lines[key] = line + + ########### List layout: ############## + + # Add two labels for selecting all/none: + hlayout = QHBoxLayout() + labelAll = QLabel("All") + labelAll.mousePressEvent = self.labelAll_click + labelNone = QLabel("None") + labelNone.mousePressEvent = self.labelNone_click + hlayout.addWidget(labelAll) + hlayout.addWidget(labelNone) + listlayout.addLayout(hlayout) + + # Add the entire list + self.checklist = QListWidget() + self.checklist.itemChanged.connect(self.item_change) + listitems = {} + for key in ( + sim_res_ins | sim_res_outs | sim_res_delays | sim_res_others + ): + listitem = QListWidgetItem(key) + listitems[key] = listitem + self.checklist.addItem(listitem) + listitem.setCheckState( + Qt.Unchecked + ) # CheckState: Qt.{Unchecked, PartiallyChecked, Checked} + for key in sim_res_outs: + listitems[key].setCheckState( + Qt.Checked + ) # CheckState: Qt.{Unchecked, PartiallyChecked, Checked} + self.checklist.setFixedWidth(150) + listlayout.addWidget(self.checklist) + + # Add a "legend" checkbox, connected to the plot. + self.legend_checkbox = QCheckBox("&Legend") + self.legend_checkbox.stateChanged.connect(self.legend_checkbox_change) + self.legend_checkbox.setCheckState(Qt.Checked) + listlayout.addWidget(self.legend_checkbox) + + # Add "Close" buttons + buttonClose = QPushButton("&Close", self) + buttonClose.clicked.connect(self.close) + listlayout.addWidget(buttonClose) + + # Done. Tell the functions below to redraw the canvas when needed. + # self.plotcanvas.draw() + self._auto_redraw = True + + def legend_checkbox_change(self, checkState): + self.plotcanvas.legend.set(visible=(checkState == Qt.Checked)) + if self._auto_redraw: + if checkState == Qt.Checked: + self.plotcanvas.legend = self.plotcanvas.axes.legend() + self.plotcanvas.draw() + # self.plotcanvas.legend + # if checkState == Qt.Checked: + # print('legend on') + # else: + # print('legend off') + + def labelAll_click(self, event): + for x in range(self.checklist.count()): + self.checklist.item(x).setCheckState(Qt.Checked) + + def labelNone_click(self, event): + for x in range(self.checklist.count()): + self.checklist.item(x).setCheckState(Qt.Unchecked) + + def item_change(self, listitem): + key = listitem.text() + self._lines[key][0].set(visible=(listitem.checkState() == Qt.Checked)) + if self._auto_redraw: + if self.legend_checkbox.checkState == Qt.Checked: + self.plotcanvas.legend = self.plotcanvas.axes.legend() + self.plotcanvas.draw() + # print(f"lines[{key}].set(visible={listitem.checkState() == Qt.Checked}). autodraw={self._auto_redraw}") + # print("Arg:", listitem) + + +# Simple test of the dialog +if __name__ == "__main__": + app = QApplication(sys.argv) + # sim_res = {"c1": [3, 6, 7], "c2": [4, 5, 5], "bfly1.0": [7, 0, 0], "bfly1.1": [-1, 0, 2], "0": [1, 2, 3]} + sim_res = { + '0': [0.5, 0.5, 0, 0], + 'add1': [0.5, 0.5, 0, 0], + 'cmul1': [0, 0.5, 0, 0], + 'cmul2': [0.5, 0, 0, 0], + 'in1': [1, 0, 0, 0], + 't1': [0, 1, 0, 0], + } + win = PlotWindow( + window=None, sim_result=sim_res, sfg_name="hej", logger=print + ) + win.exec_() + # win.show() + + +# This is the original, used as a quick reference. Do not instantiate this: +class Plot(FigureCanvas): + def __init__( + self, simulation, sfg, window, parent=None, width=5, height=4, dpi=100 + ): + self.simulation = simulation + self.sfg = sfg + self.dpi = dpi + self._window = window + + fig = Figure(figsize=(width, height), dpi=dpi) + fig.suptitle(sfg.name, fontsize=20) + self.axes = fig.add_subplot(111) + + FigureCanvas.__init__(self, fig) + self.setParent(parent) + + FigureCanvas.setSizePolicy( + self, QSizePolicy.Expanding, QSizePolicy.Expanding + ) + FigureCanvas.updateGeometry(self) + self.save_figure = QShortcut(QKeySequence("Ctrl+S"), self) + self.save_figure.activated.connect(self._save_plot_figure) + self._plot_values_sfg() + + def _save_plot_figure(self): + self._window.logger.info(f"Saving plot of figure: {self.sfg.name}.") + file_choices = "PNG (*.png)|*.png" + path, ext = QFileDialog.getSaveFileName( + self, "Save file", "", file_choices + ) + path = path.encode("utf-8") + if not path[-4:] == file_choices[-4:].encode("utf-8"): + path += file_choices[-4:].encode("utf-8") + + if path: + self.print_figure(path.decode(), dpi=self.dpi) + self._window.logger.info( + f"Saved plot: {self.sfg.name} to path: {path}." + ) + + def _plot_values_sfg(self): + x_axis = list(range(len(self.simulation.results["0"]))) + for _output in range(self.sfg.output_count): + y_axis = self.simulation.results[str(_output)] + self.axes.plot(x_axis, y_axis) -- GitLab