From 9a926dafb289196e32320456603b5a6c0a344444 Mon Sep 17 00:00:00 2001 From: turtlebasket Date: Fri, 16 Jul 2021 09:19:00 -0700 Subject: [PATCH] Refactor --- .gitignore | 1 + app.py | 104 ++++++- app.ui => archive/app.ui | 250 ++++++++++------- archive/widgets.py | 235 ++++++++++++++++ assets/eq3.png | Bin 0 -> 7787 bytes crds_calc.py | 20 +- requirements.txt | 5 + ui/mainwin.ui | 591 +++++++++++++++++++++++++++++++++++++++ widgets.py | 212 +------------- 9 files changed, 1105 insertions(+), 313 deletions(-) rename app.ui => archive/app.ui (80%) create mode 100644 archive/widgets.py create mode 100644 assets/eq3.png create mode 100644 requirements.txt create mode 100644 ui/mainwin.ui diff --git a/.gitignore b/.gitignore index c53e277..cb7d96b 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,4 @@ cython_debug/ # Local stuff to exclude .vscode/ +mainwin.py \ No newline at end of file diff --git a/app.py b/app.py index f470645..0394d58 100644 --- a/app.py +++ b/app.py @@ -1,14 +1,112 @@ import sys -from PyQt5 import QtGui, QtWidgets, uic +import crds_calc +from pandas import read_csv +from PyQt5 import QtGui, QtWidgets, QtCore from memdb import mem +from mainwin import Ui_MainWindow +from widgets import BaseGraph +import pathlib + +class AppWindow(QtWidgets.QMainWindow, Ui_MainWindow): + + csv_selected = QtCore.pyqtSignal() + correlation_complete = QtCore.pyqtSignal() + fitting_complete = QtCore.pyqtSignal() -class AppWindow(QtWidgets.QMainWindow): def __init__(self): super(AppWindow, self).__init__() - uic.loadUi('app.ui', self) + self.setupUi(self) + + # Signals + + # Graphing actions + + self.csv_selected.connect(self.raw_data_graph.plot) + def corr_act(): + self.groups_graph.plot() + self.graph_tabs.setCurrentIndex(1) + self.correlation_complete.connect(corr_act) + + # Helpers + + def display_warning(message: str): + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Warning) + msg.setText("Warning") + msg.setInformativeText(message) + msg.setWindowTitle("Warning") + msg.exec_() + + def display_error(message: str): + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Critical) + msg.setText("Error") + msg.setInformativeText(message) + msg.setWindowTitle("Error") + msg.exec_() + + def select_csv(): + filename, _ = QtWidgets.QFileDialog.getOpenFileName(self) + data = None + try: + data = read_csv(filename, comment="%", delimiter=";").to_numpy() + except: + return + mem['x_data'] = data.transpose()[0] + mem['y_data'] = data.transpose()[1] + try: + mem['v_data'] = data.transpose()[2] + except IndexError: + display_warning('No voltage column detected. VThreshold algo will not work.') + self.csv_selected.emit() + + + # Universal Actions stuff + + self.actionOpen_CSV_File.triggered.connect(select_csv) + self.actionGithub_Repository.triggered.connect(lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/turtlebasket/crds_analyze'))) + + # Inputs + + def init_correlate(): + groups_raw = None + algo = self.combo_grouping_algo.currentIndex() + try: + if algo == 0: + display_error('VThreshold not yet implemented.') + return + elif algo == 1: + groups_raw = crds_calc.spaced_groups( + mem['x_data'], + mem['y_data'], + self.spin_group_len.value(), + self.spin_min_peakheight.value(), + self.spin_min_peakprominence.value(), + self.spin_moving_average_denom.value() + ) + + mem['groups_correlated'] = crds_calc.correlate_groups(groups_raw) + self.correlation_complete.emit() + + except KeyError: + display_error('Failed to correlate. Did you import a data file & set parameters?') + self.correlate_button.pressed.connect(init_correlate) + + # Show equation + + pix = QtGui.QPixmap(f"{pathlib.Path(__file__).parent.resolve()}/assets/eq3.png") + item = QtWidgets.QGraphicsPixmapItem(pix) + item.setScale(0.38) + scene = QtWidgets.QGraphicsScene() + scene.addItem(item) + self.equation_view.setScene(scene) + + # Show self + self.show() if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) window = AppWindow() + window.show() sys.exit(app.exec_()) \ No newline at end of file diff --git a/app.ui b/archive/app.ui similarity index 80% rename from app.ui rename to archive/app.ui index ad17675..f8799bb 100644 --- a/app.ui +++ b/archive/app.ui @@ -7,7 +7,7 @@ 0 0 1091 - 580 + 611 @@ -84,9 +84,9 @@ 10 - 70 + 120 241 - 141 + 111 @@ -103,9 +103,9 @@ 10 - 20 + 50 221 - 111 + 31 @@ -118,7 +118,7 @@ 0 0 221 - 71 + 21 @@ -148,8 +148,8 @@ 0 0 - 221 - 103 + 211 + 21 @@ -170,64 +170,52 @@ - - - - Min peak height - - - - - - - 6 - - - 0.000400000000000 - - - - - - - Min peak prominence - - - - - - - 6 - - - 0.001200000000000 - - - - - - - Moving average size - - - - - - - 20 - - - + + + + 10 + 20 + 221 + 19 + + + + + VThreshold (Voltage column required) + + + + + SpacedGroups + + + + + + + 10 + 80 + 221 + 20 + + + + Every other group (MIRRORED) + + + true + + 60 - 220 + 250 151 28 @@ -248,9 +236,9 @@ 10 - 320 + 350 241 - 171 + 181 @@ -267,9 +255,9 @@ 10 - 80 + 90 221 - 89 + 81 @@ -323,7 +311,7 @@ 10 20 221 - 51 + 61 @@ -332,7 +320,7 @@ 60 - 500 + 540 151 28 @@ -349,51 +337,11 @@ Fit - - - - 10 - 10 - 241 - 51 - - - - - 8 - 75 - true - - - - Grouping Algorithm - - - - - 10 - 20 - 221 - 21 - - - - - VThreshold (V column required) - - - - - SpacedGroups - - - - 10 - 260 + 290 241 51 @@ -406,7 +354,7 @@ - Peak Isolation Config + Peak Isolation Config (timescale: ticks) false @@ -424,15 +372,101 @@ - Individual Peak Range + Overlapped Peak Range - + + + 100000 + + + 100 + + + 5000 + + + + + + + + + + 10 + 10 + 241 + 101 + + + + + 8 + 75 + true + + + + Peak Detection Config + + + + + 10 + 20 + 221 + 74 + + + + + + + Min peak height + + + + + 6 + + 0.000400000000000 + + + + + + + Min peak prominence + + + + + + + 6 + + + 0.001200000000000 + + + + + + + Moving average size + + + + + + + 20 + @@ -445,7 +479,7 @@ 0 0 1091 - 26 + 21 diff --git a/archive/widgets.py b/archive/widgets.py new file mode 100644 index 0000000..ae87e39 --- /dev/null +++ b/archive/widgets.py @@ -0,0 +1,235 @@ +import numpy as np +from pandas import read_csv +import matplotlib +matplotlib.use('Qt5Agg') +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar +from matplotlib.figure import Figure +import crds_calc +import PyQt5 +from PyQt5 import QtWidgets, QtCore +from memdb import mem +import pathlib + +# Helper functions + +class Global(QtWidgets.QWidget): + csv_selected = QtCore.pyqtSignal() + correlation_complete = QtCore.pyqtSignal() + fitting_complete = QtCore.pyqtSignal() +globj = Global() + +def display_warning(message: str): + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.warning) + msg.setText("Warning") + msg.setInformativeText(message) + msg.setWindowTitle("Warning") + msg.exec_() + +def display_error(message: str): + msg = QtWidgets.QMessageBox() + msg.setIcon(QtWidgets.QMessageBox.Critical) + msg.setText("Error") + msg.setInformativeText(message) + msg.setWindowTitle("Error") + msg.exec_() + + +# Menu definitions + +class file_menu(QtWidgets.QMenu): + def __init__(self, x): + super().__init__(x) + open_csv = QtWidgets.QAction("Open CSV File", self) + open_csv.setShortcut("Ctrl+O") + open_csv.triggered.connect(self.select_csv) + self.addAction(open_csv) + + def select_csv(self): + filename, _ = QtWidgets.QFileDialog.getOpenFileName(self) + data = read_csv(filename, comment="%", delimiter=";").to_numpy() + mem['x_data'] = data.transpose()[0] + mem['y_data'] = data.transpose()[1] + try: + mem['v_data'] = data[2].transpose() + except IndexError: + display_error('No voltage column detected. Functionality will be limited.') + + globj.csv_selected.emit() + +class help_menu(QtWidgets.QMenu): + def __init__(self, x): + super().__init__(x) + visit_repo = QtWidgets.QAction("Go to GitHub Repo", self) + visit_repo.triggered.connect(self.go_to_repo) + self.addAction(visit_repo) + + def go_to_repo(self): + PyQt5.QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/turtlebasket/crds_analyze')) + +# Inputs / Parameter boxes + +class combo_grouping_algo(QtWidgets.QComboBox): + def change(self): + mem['grouping_algo'] = self.currentIndex() + globj.grouping_algo_changed.emit() + def __init__(self, x): + super().__init__(x) + self.currentIndexChanged.connect(self.change) + +class config_area(QtWidgets.QStackedWidget): + def __init__(self, x): + super().__init__(x) + self.setCurrentIndex(0) + globj.grouping_algo_changed.connect(lambda: self.setCurrentIndex(mem['grouping_algo'])) + +class spin_min_voltage(QtWidgets.QDoubleSpinBox): + def changeVal(self, val): + mem['min_voltage'] = float(val) + def __init__(self, x): + super().__init__(x) + mem['min_voltage'] = float(self.value()) + self.textChanged.connect(self.changeVal) + +class spin_group_len(QtWidgets.QDoubleSpinBox): + def changeVal(self, val): + mem['group_len'] = float(val) + def __init__(self, x): + super().__init__(x) + mem['group_len'] = float(self.value()) + self.textChanged.connect(self.changeVal) + +class spin_peak_len(QtWidgets.QDoubleSpinBox): + def changeVal(self, val): + mem['peak_len'] = float(val) + def __init__(self, x): + super().__init__(x) + mem['peak_len'] = float(self.value()) + self.textChanged.connect(self.changeVal) + +class spin_min_peakheight(QtWidgets.QDoubleSpinBox): + def changeVal(self, val): + mem['peak_minheight'] = float(val) + def __init__(self, x): + super().__init__(x) + mem['peak_minheight'] = float(self.value()) + self.textChanged.connect(self.changeVal) + +class spin_min_peakprominence(QtWidgets.QDoubleSpinBox): + def changeVal(self, val): + mem['peak_prominence'] = float(val) + def __init__(self, x): + super().__init__(x) + mem['peak_prominence'] = float(self.value()) + self.textChanged.connect(self.changeVal) + +class spin_moving_average_denom(QtWidgets.QSpinBox): + def changeVal(self, val): + mem['moving_avg_denom'] = int(val) + print(isinstance(val, str)) + def __init__(self, x): + super().__init__(x) + mem['moving_avg_denom'] = float(self.value()) + self.textChanged.connect(self.changeVal) + +class equation_view(QtWidgets.QGraphicsView): + def __init__(self, x): + super().__init__(x) + pix = PyQt5.QtGui.QPixmap(f"{pathlib.Path(__file__).parent.resolve()}/assets/eq3.png") + item = QtWidgets.QGraphicsPixmapItem(pix) + item.setScale(0.38) + scene = QtWidgets.QGraphicsScene() + scene.addItem(item) + self.setScene(scene) + +class correlate_button(QtWidgets.QPushButton): + def calc(self): + groups_raw = None + try: + if (mem['grouping_algo'] == 0): + display_error('VThreshold not yet implemented.') + return + elif (mem['grouping_algo'] == 1): + groups_raw = crds_calc.spaced_groups() + + mem['groups_correlated'] = crds_calc.correlate_groups(groups_raw) + globj.correlation_complete.emit() + + except KeyError: + display_error('Failed to correlate. Did you import a data file & set parameters?') + + + def __init__(self, x): + super().__init__(x) + self.pressed.connect(self.calc) + +class fit_button(QtWidgets.QPushButton): + def fit(self): + print("hi") + def __init__(self, x): + super().__init__(x) + self.pressed.connect(self.fit) + + +# Graph stuff + +class graph_tab(QtWidgets.QTabWidget): + def __init__(self, x): + super().__init__(x) + globj.csv_selected.connect(lambda: self.setCurrentIndex(0)) + globj.correlation_complete.connect(lambda: self.setCurrentIndex(1)) + globj.fitting_complete.connect(lambda: self.setCurrentIndex(2)) + +class MplCanvas(FigureCanvasQTAgg): + def __init__(self, parent=None, width=5, height=4, dpi=100): + fig = Figure(figsize=(width, height), dpi=dpi) + self.axes = fig.add_subplot(111) + fig.tight_layout() + super(MplCanvas, self).__init__(fig) + +class base_graph(QtWidgets.QWidget): + """ + Widget with embedded matplotlib graph & navigation toolbar + + Reference: https://www.mfitzp.com/tutorials/plotting-matplotlib/ + """ + + canv = None + + def __init__(self, x): + super().__init__(x) + self.canv = MplCanvas(self) + # Example + # canv.axes.plot([0,1,2,3,4], [10,1,20,3,40]) + toolbar = NavigationToolbar(self.canv, self) + layout = QtWidgets.QVBoxLayout() + layout.addWidget(toolbar) + layout.addWidget(self.canv) + self.setLayout(layout) + + def plot(self): + self.canv.axes.plot(mem['x_data'], mem['y_data']) + + def plot_full(self): + self.canv.axes.clear() + self.plot() + self.canv.draw() + print("attempted plot") + +class rawdata_graph(base_graph): + def __init__(self, x): + super().__init__(x) + globj.csv_selected.connect(self.plot_full) + +class peaks_graph(base_graph): + def __init__(self, x): + super().__init__(x) + globj.correlation_complete.connect(self.plot_full) + + def plot(self): + for i in mem['groups_correlated']: + self.canv.axes.plot(i) + + +class timeconstant_graph(base_graph): + pass \ No newline at end of file diff --git a/assets/eq3.png b/assets/eq3.png new file mode 100644 index 0000000000000000000000000000000000000000..177264f9eb0cc580a48ae53319af6f14928fcfda GIT binary patch literal 7787 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 + + MainWindow + + + + 0 + 0 + 1091 + 611 + + + + CRDS Scanalyzer + + + QGroupBox: {font-style: bold; font-size: 8px } + + + + + + 260 + 0 + 821 + 561 + + + + + 8 + + + + 0 + + + + Raw Data + + + + + -1 + -1 + 811 + 531 + + + + + + + Groups + + + + + 0 + 0 + 651 + 531 + + + + + + + Time Constant + + + + + 0 + 0 + 651 + 531 + + + + + + + + + 10 + 120 + 241 + 111 + + + + + 8 + 75 + true + + + + Grouping Config + + + + + 10 + 50 + 221 + 31 + + + + 0 + + + + + + 0 + 0 + 221 + 22 + + + + + + + Minimum voltage out + + + + + + + 6 + + + 0.001000000000000 + + + + + + + + + + + 0 + 0 + 211 + 22 + + + + + + + Estimated pass time + + + + + + + 6 + + + 0.000600000000000 + + + + + + + + + + + 10 + 20 + 221 + 19 + + + + + VThreshold (Voltage column required) + + + + + SpacedGroups + + + + + + + 10 + 80 + 221 + 20 + + + + Every other group (MIRRORED) + + + true + + + + + + + 60 + 240 + 151 + 28 + + + + + -1 + + + + * {font-size: 13px} + + + Correlate + + + + + + 10 + 340 + 241 + 181 + + + + + 8 + 75 + true + + + + Initial Fit Config + + + + + 10 + 90 + 221 + 83 + + + + + + + τ value + + + + + + + 6 + + + + + + + y0 value + + + + + + + 6 + + + + + + + a value + + + + + + + 6 + + + + + + + + + 10 + 20 + 221 + 61 + + + + + + + + 60 + 530 + 151 + 28 + + + + + -1 + + + + * {font-size: 13px} + + + Fit + + + + + + 10 + 280 + 241 + 51 + + + + + 8 + 75 + true + + + + Peak Isolation Config (timescale: ticks) + + + false + + + + + 9 + 20 + 221 + 25 + + + + + + + Overlapped Peak Range + + + + + + + 100000 + + + 100 + + + 5000 + + + + + + + + + + 10 + 10 + 241 + 101 + + + + + 8 + 75 + true + + + + Peak Detection Config + + + + + 10 + 20 + 221 + 76 + + + + + + + Min peak height + + + + + + + 6 + + + 0.000400000000000 + + + + + + + Min peak prominence + + + + + + + 6 + + + 0.001200000000000 + + + + + + + Moving average size + + + + + + + 20 + + + + + + + + + + + 0 + 0 + 1091 + 26 + + + + + File + + + + + + + + + + + Help + + + + + + + + + + Open CSV File + + + Ctrl+O + + + + + Quit + + + + + Export + + + Ctrl+E + + + + + GitHub Repo + + + + + Open LabView File + + + + + Open CSV File + + + Ctrl+O + + + + + Quit + + + + + Export Binary + + + + + Open LabView Data + + + + + Github Repository + + + F1 + + + + + + RawDataGraph + QWidget +
widgets
+ 1 +
+ + PeaksGraph + QWidget +
widgets
+ 1 +
+ + TimeConstantGraph + QWidget +
widgets
+ 1 +
+
+ + +
diff --git a/widgets.py b/widgets.py index 8f71872..107c49a 100644 --- a/widgets.py +++ b/widgets.py @@ -1,192 +1,9 @@ -import numpy as np -from pandas import read_csv +from PyQt5 import QtWidgets import matplotlib matplotlib.use('Qt5Agg') from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure -import crds_calc -import PyQt5 -from PyQt5 import QtWidgets, QtCore from memdb import mem -import pathlib - -# Helper functions - -def display_warning(message: str): - msg = QtWidgets.QMessageBox() - msg.setIcon(QtWidgets.QMessageBox.warning) - msg.setText("Warning") - msg.setInformativeText(message) - msg.setWindowTitle("Warning") - msg.exec_() - -def display_error(message: str): - msg = QtWidgets.QMessageBox() - msg.setIcon(QtWidgets.QMessageBox.Critical) - msg.setText("Error") - msg.setInformativeText(message) - msg.setWindowTitle("Error") - msg.exec_() - -# Create global object to send signals - -class Global(QtWidgets.QWidget): - grouping_algo_changed = QtCore.pyqtSignal() - csv_selected = QtCore.pyqtSignal() - correlation_complete = QtCore.pyqtSignal() - fitting_complete = QtCore.pyqtSignal() -globj = Global() - -# Menu definitions - -class file_menu(QtWidgets.QMenu): - def __init__(self, x): - super().__init__(x) - open_csv = QtWidgets.QAction("Open CSV File", self) - open_csv.setShortcut("Ctrl+O") - open_csv.triggered.connect(self.select_csv) - self.addAction(open_csv) - - def select_csv(self): - filename, _ = QtWidgets.QFileDialog.getOpenFileName(self) - data = read_csv(filename, comment="%", delimiter=";").to_numpy() - mem['x_data'] = data.transpose()[0] - mem['y_data'] = data.transpose()[1] - try: - mem['v_data'] = data[2].transpose() - except IndexError: - display_error('No voltage column detected. Functionality will be limited.') - - globj.csv_selected.emit() - -class help_menu(QtWidgets.QMenu): - def __init__(self, x): - super().__init__(x) - visit_repo = QtWidgets.QAction("Go to GitHub Repo", self) - visit_repo.triggered.connect(self.go_to_repo) - self.addAction(visit_repo) - - def go_to_repo(self): - PyQt5.QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/turtlebasket/crds_analyze')) - -# Inputs / Parameter boxes - -class combo_grouping_algo(QtWidgets.QComboBox): - def change(self): - mem['grouping_algo'] = self.currentIndex() - globj.grouping_algo_changed.emit() - def __init__(self, x): - super().__init__(x) - self.currentIndexChanged.connect(self.change) - -class config_area(QtWidgets.QStackedWidget): - def __init__(self, x): - super().__init__(x) - self.setCurrentIndex(0) - globj.grouping_algo_changed.connect(lambda: self.setCurrentIndex(mem['grouping_algo'])) - -class spin_min_voltage(QtWidgets.QDoubleSpinBox): - def changeVal(self, val): - mem['min_voltage'] = float(val) - def __init__(self, x): - super().__init__(x) - mem['min_voltage'] = float(self.value()) - self.textChanged.connect(self.changeVal) - -class spin_group_len(QtWidgets.QDoubleSpinBox): - def changeVal(self, val): - mem['group_len'] = float(val) - def __init__(self, x): - super().__init__(x) - mem['group_len'] = float(self.value()) - self.textChanged.connect(self.changeVal) - -class spin_peak_len(QtWidgets.QDoubleSpinBox): - def changeVal(self, val): - mem['peak_len'] = float(val) - def __init__(self, x): - super().__init__(x) - mem['peak_len'] = float(self.value()) - self.textChanged.connect(self.changeVal) - -class spin_min_peakheight(QtWidgets.QDoubleSpinBox): - def changeVal(self, val): - mem['peak_minheight'] = float(val) - def __init__(self, x): - super().__init__(x) - mem['peak_minheight'] = float(self.value()) - self.textChanged.connect(self.changeVal) - -class spin_min_peakprominence(QtWidgets.QDoubleSpinBox): - def changeVal(self, val): - mem['peak_prominence'] = float(val) - def __init__(self, x): - super().__init__(x) - mem['peak_prominence'] = float(self.value()) - self.textChanged.connect(self.changeVal) - -class spin_moving_average_denom(QtWidgets.QSpinBox): - def changeVal(self, val): - mem['moving_avg_denom'] = int(val) - print(isinstance(val, str)) - def __init__(self, x): - super().__init__(x) - mem['moving_avg_denom'] = float(self.value()) - self.textChanged.connect(self.changeVal) - -class equation_view(QtWidgets.QGraphicsView): - def __init__(self, x): - super().__init__(x) - pix = PyQt5.QtGui.QPixmap(f"{pathlib.Path(__file__).parent.resolve()}/assets/eq2.png") - item = QtWidgets.QGraphicsPixmapItem(pix) - item.setScale(0.15) - scene = QtWidgets.QGraphicsScene() - scene.addItem(item) - self.setScene(scene) - -class correlate_button(QtWidgets.QPushButton): - def calc(self): - groups_raw = None - try: - if (mem['grouping_algo'] == 0): - display_error('VThreshold not yet implemented.') - elif (mem['grouping_algo'] == 1): - groups_raw = crds_calc.spaced_groups( - mem['x_data'], - mem['y_data'], - mem['group_len'], - mem['peak_minheight'], - mem['peak_prominence'], - mem['moving_avg_denom'] - ) - - mem['groups_correlated'] = crds_calc.correlate_groups(groups_raw) - globj.correlation_complete.emit() - - except KeyError: - display_error('Failed to correlate. Did you import a data file & set parameters?') - - - def __init__(self, x): - super().__init__(x) - self.pressed.connect(self.calc) - -class fit_button(QtWidgets.QPushButton): - def fit(self): - print("hi") - def __init__(self, x): - super().__init__(x) - self.pressed.connect(self.fit) - - -# Graph stuff - -class graph_tab(QtWidgets.QTabWidget): - def __init__(self, x): - super().__init__(x) - globj.csv_selected.connect(lambda: self.setCurrentIndex(0)) - globj.correlation_complete.connect(lambda: self.setCurrentIndex(1)) - globj.fitting_complete.connect(lambda: self.setCurrentIndex(2)) class MplCanvas(FigureCanvasQTAgg): def __init__(self, parent=None, width=5, height=4, dpi=100): @@ -195,7 +12,7 @@ class MplCanvas(FigureCanvasQTAgg): fig.tight_layout() super(MplCanvas, self).__init__(fig) -class base_graph(QtWidgets.QWidget): +class BaseGraph(QtWidgets.QWidget): """ Widget with embedded matplotlib graph & navigation toolbar @@ -215,29 +32,22 @@ class base_graph(QtWidgets.QWidget): layout.addWidget(self.canv) self.setLayout(layout) - def plot(self): + def plot_data(self): self.canv.axes.plot(mem['x_data'], mem['y_data']) - def plot_full(self): + def plot(self): self.canv.axes.clear() - self.plot() + self.plot_data() self.canv.draw() print("attempted plot") -class rawdata_graph(base_graph): - def __init__(self, x): - super().__init__(x) - globj.csv_selected.connect(self.plot_full) +class RawDataGraph(BaseGraph): + pass -class peaks_graph(base_graph): - def __init__(self, x): - super().__init__(x) - globj.correlation_complete.connect(self.plot_full) - - def plot(self): +class PeaksGraph(BaseGraph): + def plot_data(self): for i in mem['groups_correlated']: self.canv.axes.plot(i) - -class timeconstant_graph(base_graph): - pass \ No newline at end of file +class TimeConstantGraph(BaseGraph): + pass # no modifications thus far \ No newline at end of file