Newer
Older
"""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 ( # QFrame,; QScrollArea,; QLineEdit,; QSizePolicy,; QLabel,; QFileDialog,; QShortcut,
QApplication,
QCheckBox,
QHBoxLayout,
QListWidget,
QListWidgetItem,
QPushButton,
from b_asic import Simulation
from b_asic.operation import ResultKey
from b_asic.types import Num
Dialog for plotting the result of simulations.
Parameters
----------
sim_result : dict
Simulation results of the form obtained from :attr:`~b_asic.simulation.Simulation.results`.
Alternative, sim_result can be a list, ['name1', sim_result1, 'name2', sim_result2, ...]
sfg_name : str, optional
The name of the SFG.
parent : optional
The parent window.
"""
def __init__(
self,
sim_result: Mapping[ResultKey, Sequence[Num]],
figure_name: Optional[str] = None,
self.setWindowFlags(
Qt.WindowTitleHint
| Qt.WindowCloseButtonHint
| Qt.WindowMinimizeButtonHint
| Qt.WindowMaximizeButtonHint
)
f"Simulation results: {figure_name}"
if figure_name is not None
else "Simulation results"
)
self.setWindowTitle(title)
self._auto_redraw = False
########### Flattening sim_result, if it is a list of results #######
# take: sim_result (possibly on form ['name1', simres1, 'name2', simres2, ...]
# generate: sim_result (dict)
if isinstance(sim_result, Simulation):
sim_result = sim_result._results
assert isinstance(sim_result, dict), TypeError(
"Parsing sim_result as a Simulation, but the _result seems broken."
)
elif isinstance(sim_result, list):
new_result = dict()
nr = 0 # value number. Used for automatic names.
name = None
for element in sim_result:
if isinstance(element, str):
assert not name, Exception(
"Parsing sim_result as a list. Did you provide two names after"
" each other?"
)
name = element
else:
if not name:
nr = nr + 1
name = "(res" + str(nr) + ")"
if isinstance(element, dict):
res = element
elif isinstance(element, Simulation):
res = element._results
assert isinstance(res, dict), TypeError(
f"Parsing sim_result as a list. Result '{name}' is a"
" Simulation, and its _result seems broken."
)
else:
raise TypeError(
"Parsing sim_result as a list. Supported results are dict"
f" and Simulation, but result '{name}' is {type(element)}"
)
for key, result in res.items():
if re.fullmatch("[0-9]+", key): # it's an output
key = "out" + key
new_result[name + "." + key] = result
name = None
assert not name, Exception(
"Parsing sim_result as a list. Did you provide a name as the last item"
" in the list?"
)
sim_result = new_result
elif not isinstance(sim_result, dict):
raise TypeError(
"sim_result must be a dict, Simulation or list."
f" Found {type(sim_result)}"
########### Categorizing/sorting/renaming sim_results: ##############
# take: sim_result
# 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(r"(.*\.)?in[0-9]+", key):
elif re.fullmatch(r"[0-9]+", key): # It's an output as just a number.
key2 = 'out' + key
elif re.fullmatch(
r"(.*\.)?out[0-9]+", key
): # It's an output already formulated.
m = 3
elif re.fullmatch(r"(.*\.)?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: ############################################
# | ... | 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 = {}
markers = ".ov<^>s+*xd|_"
fmt = '-'
ix = 0 # index bytes in markers
if len(updated_result[key]) <= 100:
fmt = markers[ix] + '-'
ix = (ix + 1) % len(markers)
else:
fmt = '-'
line = self._plot_axes.plot(updated_result[key], fmt, label=key)
self._plot_canvas = FigureCanvas(self._plot_fig)
self._toolbar = NavigationToolbar(self._plot_canvas, self)
plotlayout.addWidget(self._plot_canvas)
########### List layout: ##############
# Add two buttons for selecting all/none:
hlayout = QHBoxLayout()
self._button_all.clicked.connect(self._button_all_click)
hlayout.addWidget(self._button_all)
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 = {}
list_item = QListWidgetItem(key)
listitems[key] = list_item
self._checklist.addItem(list_item)
list_item.setCheckState(
Qt.CheckState.Unchecked # CheckState: Qt.CheckState.{Unchecked, PartiallyChecked, Checked}
)
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.setCheckState(Qt.CheckState.Checked)
self._legend_checkbox.stateChanged.connect(self._legend_checkbox_change)
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)
# Done. Tell the functions below to redraw the canvas when needed.
# self.plotcanvas.draw()
self._auto_redraw = True
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
def add_result(self, name, result) -> None:
"""
Add another result to the plot.
The new signals are added in natural order without sorting them.
---
name: str
The name of the result
result: dict
The result from one simulation.
"""
markers = ".ov<^>s+*xd|_"
ix = 0
self._auto_redraw = False
for key, res in result.items():
key2 = name + "." + key
ischecked = Qt.CheckState.Unchecked
if re.fullmatch(r"[0-9]+", key):
key2 = name + '.out' + key
ischecked = Qt.CheckState.Checked
if len(res) <= 100:
fmt = markers[ix] + '-'
ix = (ix + 1) % len(markers)
else:
fmt = '-'
def addline(key, vector, checked):
line = self._plot_axes.plot(np.real(res), fmt, label=key)
self._lines[key] = line[0]
list_item = QListWidgetItem(key)
list_item.setCheckState(
Qt.CheckState.Unchecked
) # will add it if checked
self._checklist.addItem(list_item)
self._lines[
key
].remove() # remove the line from plot. Keep it in _lines.
list_item.setCheckState(checked) # will add it if checked
if all(np.imag(np.real_if_close(res)) == 0):
# real: add one line with corresponding checkbox
addline(key2, np.real(res), ischecked)
else:
# complex: add '_re', '_im', '_mag', '_ang'
addline(key2 + "_re", np.real(res), ischecked)
addline(key2 + "_im", np.imag(res), ischecked)
addline(key2 + "_mag", np.absolute(res), Qt.CheckState.Unchecked)
addline(key2 + "_ang", np.angle(res), Qt.CheckState.Unchecked)
self._auto_redraw = True
self._update_legend()
self._plot_canvas.draw()
def _legend_checkbox_change(self, check_state):
self._legend.set(visible=(check_state == Qt.CheckState.Checked))
self._update_legend()
self._plot_canvas.draw()
# def _ontop_checkbox_change(self, checkState):
# Bugg: It seems the window closes if you change the WindowStaysOnTopHint.
# (Nothing happens if "changing" from False to False or True to True)
# self.setWindowFlag(Qt.WindowStaysOnTopHint, on = (checkState == Qt.CheckState.Checked))
# self.setWindowFlag(Qt.WindowStaysOnTopHint, on = True)
# print("_ontop_checkbox_change")
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._plot_canvas.draw()
# if self._legend_checkbox.checkState == Qt.CheckState.Checked:
if self._legend_checkbox.isChecked():
self._legend = self._plot_axes.legend()
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._plot_canvas.draw()
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._plot_canvas.draw()
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
):
"""Deprecated. Use `show_simulation_result` instead."""
show_simulation_result(sim_results, sfg_name)
def show_simulation_result(
sim_results: Dict[str, List[complex]], figure_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`.
Alternative, sim_result can be a list, ['name1', sim_result1, 'name2', sim_result2, ...]
sfg_name : str, optional
if not QApplication.instance():
app = QApplication(sys.argv)
else:
app = QApplication.instance()
win = PlotWindow(sim_result=sim_results, figure_name=figure_name)
# Simple test of the dialog
if __name__ == "__main__":
'0': [1.5, 1.6, 1.5, 1],
'1': [1.0, 2.0, 1.5, 1.1],
'add1': [1.5, 1.5, 1, 1],
'cmul1': [1, 1.5, 1, 1],
'cmul2': [1.5, 1, 1, 1],
'in1': [2, 1, 1, 1],
'in2': [1.1, 3, 1, 1],
't1': [1, 2, 1, 1],
't2': [1, 1, 2, 1],
't3': [1, 1, 1, 2],
}
'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],
't1': [0, 1, 0, 0],
't2': [0, 0, 1, 0],
't3': [0, 0, 0, 1],
#'0': np.random.rand(50).tolist(),
'0': np.random.rand(50),
'1': np.random.rand(200).tolist(),
}
res4 = {
'0': np.random.rand(60).tolist(),
'1': np.random.rand(220).tolist(),
't4': np.random.rand(50).tolist(),
}
# start_simulation_dialog(res3)
app = QApplication(sys.argv)
win2 = PlotWindow(['Real', res1, 'Cpx', res2, res3], "Test data")
win2.add_result('res4', res4)
win2.show()
app.exec_()