chore: декомпозирован класс PlotWidget, нормализован обмен данными

This commit is contained in:
Andrew 2025-01-28 11:11:47 +03:00
parent 05ee548704
commit e4a7b39307
3 changed files with 70 additions and 53 deletions

View File

@ -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))

View File

@ -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)

View File

@ -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)