chore: декомпозирован класс PlotWidget, нормализован обмен данными
This commit is contained in:
parent
05ee548704
commit
e4a7b39307
@ -1,8 +1,8 @@
|
|||||||
import copy
|
import copy
|
||||||
import traceback
|
import traceback
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional, Tuple, List, Any
|
from typing import Optional, Tuple, Callable, List, Any
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QWidget,
|
QWidget,
|
||||||
@ -28,8 +28,7 @@ class ChannelTimings():
|
|||||||
ideal_time: float = 0.0
|
ideal_time: float = 0.0
|
||||||
client_time: float = 0.0
|
client_time: float = 0.0
|
||||||
worst_performance: float = 2
|
worst_performance: float = 2
|
||||||
worst_timeframe: list[float] = [0, 0]
|
worst_timeframe: list = field(default_factory=lambda: [0, 0])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PlotWidget(BasePlotWidget):
|
class PlotWidget(BasePlotWidget):
|
||||||
@ -43,29 +42,24 @@ class PlotWidget(BasePlotWidget):
|
|||||||
widgets_datapack = [self._build_widget(data_sample) for self._datastep, data_sample in enumerate(data)]
|
widgets_datapack = [self._build_widget(data_sample) for self._datastep, data_sample in enumerate(data)]
|
||||||
|
|
||||||
except (KeyError, ValueError, TypeError) as e:
|
except (KeyError, ValueError, TypeError) as e:
|
||||||
# Provide user-friendly log message, log traceback in debug.
|
|
||||||
logger.error(f"Возникла проблема при сборке данных графика: {e}")
|
logger.error(f"Возникла проблема при сборке данных графика: {e}")
|
||||||
logger.debug(traceback.format_exc())
|
logger.debug(traceback.format_exc())
|
||||||
error_label = QLabel("Произошла ошибка при формировании графиков. Пожалуйста, проверьте корректность данных.")
|
error_label = QLabel("Произошла ошибка при формировании графиков. Пожалуйста, проверьте корректность данных.")
|
||||||
widgets_datapack = [error_label]
|
widgets_datapack = [error_label]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Catch all remaining exceptions.
|
|
||||||
logger.error(f"Непредвиденная ошибка при формировании графиков: {e}")
|
logger.error(f"Непредвиденная ошибка при формировании графиков: {e}")
|
||||||
logger.debug(traceback.format_exc())
|
logger.debug(traceback.format_exc())
|
||||||
error_label = QLabel("Непредвиденная ошибка при формировании графиков.")
|
error_label = QLabel("Непредвиденная ошибка при формировании графиков.")
|
||||||
widgets_datapack = [error_label]
|
widgets_datapack = [error_label]
|
||||||
finally:
|
finally:
|
||||||
# Notify mediator
|
|
||||||
self._mediator.notify(self, widgets_datapack)
|
self._mediator.notify(self, widgets_datapack)
|
||||||
|
|
||||||
def _add_performance_label(self,
|
def _build_performance_label(self,
|
||||||
layout: QVBoxLayout,
|
|
||||||
timings: ChannelTimings,
|
timings: ChannelTimings,
|
||||||
qt_items: dict) -> None:
|
qt_items: dict) -> QWidget:
|
||||||
"""
|
"""
|
||||||
Добавляет QLabel с информацией о производительности.
|
Добавляет QLabel с информацией о производительности.
|
||||||
"""
|
"""
|
||||||
# TODO: Почему PlotWidget создает Label? Вынести в другое место.
|
|
||||||
tesla_TWC = round((1 - timings.TWC_time/timings.client_time)*100, 2) if timings.client_time else 0.0
|
tesla_TWC = round((1 - timings.TWC_time/timings.client_time)*100, 2) if timings.client_time else 0.0
|
||||||
tesla_ideal = round((1 - timings.ideal_time/timings.client_time)*100, 2) if timings.client_time else 0.0
|
tesla_ideal = round((1 - timings.ideal_time/timings.client_time)*100, 2) if timings.client_time else 0.0
|
||||||
TWC_ideal = round((timings.ideal_time/timings.TWC_time)*100, 2) if timings.TWC_time else 0.0
|
TWC_ideal = round((timings.ideal_time/timings.TWC_time)*100, 2) if timings.TWC_time else 0.0
|
||||||
@ -85,53 +79,39 @@ class PlotWidget(BasePlotWidget):
|
|||||||
label_layout.addWidget(kdip_label, alignment=Qt.AlignLeft)
|
label_layout.addWidget(kdip_label, alignment=Qt.AlignLeft)
|
||||||
spacer = QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Minimum)
|
spacer = QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Minimum)
|
||||||
label_layout.addSpacerItem(spacer)
|
label_layout.addSpacerItem(spacer)
|
||||||
|
|
||||||
self.set_style(label_widget)
|
self.set_style(label_widget)
|
||||||
layout.addWidget(label_widget)
|
|
||||||
|
|
||||||
qt_items["performance label"] = label_widget
|
qt_items["performance label"] = label_widget
|
||||||
qt_items["real performance"] = real_label
|
qt_items["real performance"] = real_label
|
||||||
qt_items["ideal performance"] = ideal_label
|
qt_items["ideal performance"] = ideal_label
|
||||||
qt_items["real to ideal performance"] = kdip_label
|
qt_items["real to ideal performance"] = kdip_label
|
||||||
|
return label_widget
|
||||||
|
|
||||||
def _build_widget(self, graphic_passport: GraphicPassport) -> QWidget:
|
def _build_widget(self, graphic_passport: GraphicPassport) -> QWidget:
|
||||||
# TODO: Исходя из сигнатуры метода, он создает и возвращает виджет с графиками.
|
|
||||||
# Простыня "result_widget, reg_items, curve_items, qt_items" не похожа на виджет с графиком.
|
|
||||||
"""
|
"""
|
||||||
Собирает графический виджет для одного набора данных.
|
Собирает графический виджет для одного набора данных.
|
||||||
"""
|
"""
|
||||||
container_widget, container_layout, plot_layout, pyqt_container = self._generate_widget_container()
|
container_widget, container_layout, pyqt_container = self._generate_widget_container()
|
||||||
plotter = PlotItemGenerator(graphic_passport, len(self._plt_channels), self._stage_colors)
|
plot_layout = CustomPlotLayout(graphic_passport, len(self._plt_channels), self._stage_colors, self)
|
||||||
|
plot_layout.build(pyqt_container, self._plt_channels)
|
||||||
|
|
||||||
for widget_num, (channel, description) in enumerate(self._plt_channels.items()):
|
if plot_layout.property("performance"):
|
||||||
plot_item, plot_timings = plotter.generate_plot_item(widget_num, channel, description, pyqt_container)
|
perf_widget = self._build_performance_label(
|
||||||
|
plot_layout.property("performance"),
|
||||||
if widget_num == 0:
|
pyqt_container.qt_items
|
||||||
main_plot = plot_item
|
)
|
||||||
else:
|
container_layout.addWidget(perf_widget)
|
||||||
# Связываем остальные графики с основным графиком
|
|
||||||
plot_item.setXLink(main_plot)
|
|
||||||
|
|
||||||
if description["settings"]["performance"]:
|
|
||||||
self._add_performance_label(container_layout, plot_timings, pyqt_container.qt_items)
|
|
||||||
|
|
||||||
plot_layout.addItem(plot_item, widget_num, 0)
|
|
||||||
|
|
||||||
navigator = NavigatorPlot(plot_timings.worst_timeframe, main_plot)
|
|
||||||
if navigator is not None:
|
|
||||||
plot_layout.addItem(navigator, widget_num+1, 0)
|
|
||||||
|
|
||||||
container_layout.addWidget(plot_layout)
|
container_layout.addWidget(plot_layout)
|
||||||
container_widget.setProperty("pyqt_container", pyqt_container)
|
container_widget.setProperty("pyqt_container", pyqt_container)
|
||||||
return container_widget
|
return container_widget
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_widget_container() -> Tuple[QWidget, QVBoxLayout, pg.GraphicsLayoutWidget, PlotItems]:
|
def _generate_widget_container() -> Tuple[QWidget, QVBoxLayout, PlotItems]:
|
||||||
container_widget = QWidget()
|
container_widget = QWidget()
|
||||||
container_layout = QVBoxLayout(container_widget)
|
container_layout = QVBoxLayout(container_widget)
|
||||||
plot_layout = pg.GraphicsLayoutWidget()
|
|
||||||
pyqt_container = PlotItems({"real":{}, "ideal":{}}, {"real":{}, "ideal":{}}, {})
|
pyqt_container = PlotItems({"real":{}, "ideal":{}}, {"real":{}, "ideal":{}}, {})
|
||||||
return (container_widget, container_layout, plot_layout, pyqt_container)
|
return (container_widget, container_layout, pyqt_container)
|
||||||
|
|
||||||
def _update_status(self, widgsteps:int, pointsteps:int, cur_widg:int, cur_point:int):
|
def _update_status(self, widgsteps:int, pointsteps:int, cur_widg:int, cur_point:int):
|
||||||
if self._datalen:
|
if self._datalen:
|
||||||
@ -148,14 +128,43 @@ class PlotWidget(BasePlotWidget):
|
|||||||
self._controller.update_progress(progress)
|
self._controller.update_progress(progress)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomPlotLayout(pg.GraphicsLayoutWidget):
|
||||||
|
def __init__(self,
|
||||||
|
graphic_passport: GraphicPassport,
|
||||||
|
widget_steps: int,
|
||||||
|
colors: dict, parent: PlotWidget = None) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self._plotter = PlotItemGenerator(graphic_passport, widget_steps, colors, parent)
|
||||||
|
self.setProperty("performance", None)
|
||||||
|
|
||||||
|
def build(self, pyqt_container:PlotItems, plt_channels:dict) -> None:
|
||||||
|
|
||||||
|
for widget_num, (channel, description) in enumerate(plt_channels.items()):
|
||||||
|
plot_item, plot_timings = self._plotter.generate_plot_item(widget_num, channel, description, pyqt_container)
|
||||||
|
if widget_num == 0:
|
||||||
|
main_plot = plot_item
|
||||||
|
else:
|
||||||
|
plot_item.setXLink(main_plot)
|
||||||
|
|
||||||
|
if description["Settings"]["performance"]:
|
||||||
|
self.setProperty("performance", plot_timings)
|
||||||
|
|
||||||
|
self.addItem(plot_item, widget_num, 0)
|
||||||
|
|
||||||
|
navigator = NavigatorPlot(plot_timings.worst_timeframe, main_plot)
|
||||||
|
if navigator is not None:
|
||||||
|
self.addItem(navigator, widget_num+1, 0)
|
||||||
|
|
||||||
|
|
||||||
class PlotItemGenerator:
|
class PlotItemGenerator:
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
graphic_passport: GraphicPassport,
|
graphic_passport: GraphicPassport,
|
||||||
widget_steps: int,
|
widget_steps: int,
|
||||||
colors: dict) -> None:
|
colors: dict, parent:PlotWidget = None) -> None:
|
||||||
self._stage_colors = colors
|
self._stage_colors = colors
|
||||||
self._ideal_mode = graphic_passport.dataframe is None
|
self._ideal_mode = graphic_passport.dataframe is None
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
if not self._ideal_mode:
|
if not self._ideal_mode:
|
||||||
dataframe_headers = graphic_passport.dataframe.columns.tolist()
|
dataframe_headers = graphic_passport.dataframe.columns.tolist()
|
||||||
@ -203,9 +212,8 @@ class PlotItemGenerator:
|
|||||||
is_last = (cur_point == len(points_pocket) - 1)
|
is_last = (cur_point == len(points_pocket) - 1)
|
||||||
|
|
||||||
if self._ideal_mode:
|
if self._ideal_mode:
|
||||||
timings, point_events, point_data.timeframe = self._generate_synthetic_events(timings, ideal_data)
|
timings, point_data.events, point_data.timeframe = self._generate_synthetic_events(timings, ideal_data)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if settings["force compensation FE"]:
|
if settings["force compensation FE"]:
|
||||||
force = point_data.useful_data["force"]
|
force = point_data.useful_data["force"]
|
||||||
k_hardness = useful_data.k_hardness
|
k_hardness = useful_data.k_hardness
|
||||||
@ -217,23 +225,23 @@ class PlotItemGenerator:
|
|||||||
|
|
||||||
if settings["force accuracy"]:
|
if settings["force accuracy"]:
|
||||||
force = point_data.useful_data["force"]
|
force = point_data.useful_data["force"]
|
||||||
self._add_force_accuracy_region(point_events["Welding"], force, plot_item)
|
self._add_force_accuracy_region(point_data.events["Welding"], force, plot_item)
|
||||||
|
|
||||||
if settings["ideals"] and settings["mirror ME"]:
|
if settings["ideals"] and settings["mirror ME"]:
|
||||||
for stage in point_events.keys():
|
for stage in point_data.events.keys():
|
||||||
ideal_data[stage] = self._shift_data("ME", description["Ideal_signals"], ideal_data[stage], lambda x: useful_data.range_ME-x)
|
ideal_data[stage] = self._shift_data("ME", description["Ideal_signals"], ideal_data[stage], lambda x: useful_data.range_ME-x)
|
||||||
|
|
||||||
if settings["workpiece"]:
|
if settings["workpiece"]:
|
||||||
self._add_workpiece(point_data, plot_item)
|
self._add_workpiece(point_data, plot_item)
|
||||||
|
|
||||||
if settings["ideals"]:
|
if settings["ideals"]:
|
||||||
self._add_ideal_stage_regions(plot_item, ideal_data, point_events, pyqt_container.regions, 100)
|
self._add_ideal_stage_regions(plot_item, ideal_data, point_data.events, pyqt_container.regions, 100)
|
||||||
self._add_ideal_signals(plot_item, legend, ideal_data, point_events, description["Ideal_signals"], pyqt_container.curves, is_last)
|
self._add_ideal_signals(plot_item, legend, ideal_data, point_data.events, description["Ideal_signals"], pyqt_container.curves, is_last)
|
||||||
|
|
||||||
if settings["performance"]:
|
if settings["performance"]:
|
||||||
timings = self._calc_performance(timings, point_data, ideal_data, is_last)
|
timings = self._calc_performance(timings, point_data, ideal_data, is_last)
|
||||||
|
|
||||||
self._update_status(widget_steps, point_steps, widget_num, cur_point)
|
self._parent._update_status(widget_steps, point_steps, widget_num, cur_point)
|
||||||
|
|
||||||
# Добавляем реальные сигналы
|
# Добавляем реальные сигналы
|
||||||
if not self._ideal_mode:
|
if not self._ideal_mode:
|
||||||
@ -245,7 +253,7 @@ class PlotItemGenerator:
|
|||||||
def _shift_data(valid_str: str,
|
def _shift_data(valid_str: str,
|
||||||
signals: list[dict],
|
signals: list[dict],
|
||||||
dataframe: pd.DataFrame,
|
dataframe: pd.DataFrame,
|
||||||
func: function) -> pd.DataFrame:
|
func: Callable) -> pd.DataFrame:
|
||||||
keys = dataframe.keys()
|
keys = dataframe.keys()
|
||||||
for signal in signals:
|
for signal in signals:
|
||||||
if valid_str in signal["name"] and signal["name"] in keys:
|
if valid_str in signal["name"] and signal["name"] in keys:
|
||||||
@ -453,12 +461,14 @@ class PlotItemGenerator:
|
|||||||
dataframe.loc[point_idxs] = self._shift_data("FE", real_signals, dataframe.loc[point_idxs], lambda x: x + F_comp)
|
dataframe.loc[point_idxs] = self._shift_data("FE", real_signals, dataframe.loc[point_idxs], lambda x: x + F_comp)
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
|
||||||
class NavigatorPlot(pg.PlotItem):
|
class NavigatorPlot(pg.PlotItem):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
time_region:tuple[float, float],
|
time_region:tuple[float, float],
|
||||||
main_plot: pg.PlotItem):
|
main_plot: pg.PlotItem):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._init_navigator(time_region, main_plot)
|
self._init_navigator(time_region, main_plot)
|
||||||
|
self._init_syncranisation(main_plot)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _downsample_data(x:list, y:list, max_points=5000):
|
def _downsample_data(x:list, y:list, max_points=5000):
|
||||||
@ -495,6 +505,17 @@ class NavigatorPlot(pg.PlotItem):
|
|||||||
region.setRegion([x_min, x_max])
|
region.setRegion([x_min, x_max])
|
||||||
region.blockSignals(False)
|
region.blockSignals(False)
|
||||||
|
|
||||||
|
def _init_syncranisation(self, main_plot: pg.PlotItem) -> None:
|
||||||
|
"""
|
||||||
|
Связывает изменения навигатора и других графиков друг с другом
|
||||||
|
"""
|
||||||
|
self.ROI_region.sigRegionChanged.connect(
|
||||||
|
lambda: self._sync_main_plot_with_navigator(main_plot, self.ROI_region)
|
||||||
|
)
|
||||||
|
main_plot.sigXRangeChanged.connect(
|
||||||
|
lambda _, plot=main_plot, region=self.ROI_region: self._sync_navigator_with_main(main_plot=plot, region=region)
|
||||||
|
)
|
||||||
|
|
||||||
def _init_navigator(self,
|
def _init_navigator(self,
|
||||||
time_region:tuple[float, float],
|
time_region:tuple[float, float],
|
||||||
main_plot: pg.PlotItem) -> None:
|
main_plot: pg.PlotItem) -> None:
|
||||||
@ -517,8 +538,4 @@ class NavigatorPlot(pg.PlotItem):
|
|||||||
self.addItem(self.ROI_region)
|
self.addItem(self.ROI_region)
|
||||||
self.getViewBox().setLimits(xMin=0, xMax=x[-1])
|
self.getViewBox().setLimits(xMin=0, xMax=x[-1])
|
||||||
|
|
||||||
# Связываем изменение региона навигатора с обновлением области просмотра основного графика
|
|
||||||
self.ROI_region.sigRegionChanged.connect(lambda: self._sync_main_plot_with_navigator(main_plot, self.ROI_region))
|
|
||||||
main_plot.sigXRangeChanged.connect(lambda _, plot=main_plot, region=self.ROI_region: self._sync_navigator_with_main(main_plot=plot, region=region))
|
|
||||||
|
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ class CustomTabWidget(QTabWidget):
|
|||||||
|
|
||||||
#TODO: переписать обмен данными, засунуть ссылки куда-то еще
|
#TODO: переписать обмен данными, засунуть ссылки куда-то еще
|
||||||
def create_tab(self, plot_widget:QWidget) -> None:
|
def create_tab(self, plot_widget:QWidget) -> None:
|
||||||
plot_items:PlotItems = plot_widget.property("plot_items")
|
plot_items:PlotItems = plot_widget.property("pyqt_container")
|
||||||
tab = QWidget()
|
tab = QWidget()
|
||||||
tab.setProperty("reg_items", plot_items.regions)
|
tab.setProperty("reg_items", plot_items.regions)
|
||||||
tab.setProperty("curve_items", plot_items.curves)
|
tab.setProperty("curve_items", plot_items.curves)
|
||||||
|
|||||||
@ -18,8 +18,8 @@ def main():
|
|||||||
monitor = DirectoryMonitor()
|
monitor = DirectoryMonitor()
|
||||||
file_manager = FileManager(monitor=monitor)
|
file_manager = FileManager(monitor=monitor)
|
||||||
data_converter = DataConverter()
|
data_converter = DataConverter()
|
||||||
plot_widget_builder = PlotWidget()
|
|
||||||
controller = Controller(file_manager=file_manager)
|
controller = Controller(file_manager=file_manager)
|
||||||
|
plot_widget_builder = PlotWidget(controller=controller)
|
||||||
passport_former = PassportFormer()
|
passport_former = PassportFormer()
|
||||||
window = MainWindow()
|
window = MainWindow()
|
||||||
mediator = Mediator(data_converter, passport_former, plot_widget_builder, controller, file_manager)
|
mediator = Mediator(data_converter, passport_former, plot_widget_builder, controller, file_manager)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user