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 traceback
import sys
from typing import Optional, Tuple, List, Any
from dataclasses import dataclass
from typing import Optional, Tuple, Callable, List, Any
from dataclasses import dataclass, field
from PyQt5.QtWidgets import (
QWidget,
@ -28,8 +28,7 @@ class ChannelTimings():
ideal_time: float = 0.0
client_time: float = 0.0
worst_performance: float = 2
worst_timeframe: list[float] = [0, 0]
worst_timeframe: list = field(default_factory=lambda: [0, 0])
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)]
except (KeyError, ValueError, TypeError) as e:
# Provide user-friendly log message, log traceback in debug.
logger.error(f"Возникла проблема при сборке данных графика: {e}")
logger.debug(traceback.format_exc())
error_label = QLabel("Произошла ошибка при формировании графиков. Пожалуйста, проверьте корректность данных.")
widgets_datapack = [error_label]
except Exception as e:
# Catch all remaining exceptions.
logger.error(f"Непредвиденная ошибка при формировании графиков: {e}")
logger.debug(traceback.format_exc())
error_label = QLabel("Непредвиденная ошибка при формировании графиков.")
widgets_datapack = [error_label]
finally:
# Notify mediator
self._mediator.notify(self, widgets_datapack)
def _add_performance_label(self,
layout: QVBoxLayout,
def _build_performance_label(self,
timings: ChannelTimings,
qt_items: dict) -> None:
qt_items: dict) -> QWidget:
"""
Добавляет QLabel с информацией о производительности.
"""
# TODO: Почему PlotWidget создает Label? Вынести в другое место.
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
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)
spacer = QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Minimum)
label_layout.addSpacerItem(spacer)
self.set_style(label_widget)
layout.addWidget(label_widget)
qt_items["performance label"] = label_widget
qt_items["real performance"] = real_label
qt_items["ideal performance"] = ideal_label
qt_items["real to ideal performance"] = kdip_label
return label_widget
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()
plotter = PlotItemGenerator(graphic_passport, len(self._plt_channels), self._stage_colors)
container_widget, container_layout, pyqt_container = self._generate_widget_container()
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()):
plot_item, plot_timings = 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._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)
if plot_layout.property("performance"):
perf_widget = self._build_performance_label(
plot_layout.property("performance"),
pyqt_container.qt_items
)
container_layout.addWidget(perf_widget)
container_layout.addWidget(plot_layout)
container_widget.setProperty("pyqt_container", pyqt_container)
return container_widget
@staticmethod
def _generate_widget_container() -> Tuple[QWidget, QVBoxLayout, pg.GraphicsLayoutWidget, PlotItems]:
def _generate_widget_container() -> Tuple[QWidget, QVBoxLayout, PlotItems]:
container_widget = QWidget()
container_layout = QVBoxLayout(container_widget)
plot_layout = pg.GraphicsLayoutWidget()
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):
if self._datalen:
@ -148,14 +128,43 @@ class PlotWidget(BasePlotWidget):
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:
def __init__(self,
graphic_passport: GraphicPassport,
widget_steps: int,
colors: dict) -> None:
colors: dict, parent:PlotWidget = None) -> None:
self._stage_colors = colors
self._ideal_mode = graphic_passport.dataframe is None
self._parent = parent
if not self._ideal_mode:
dataframe_headers = graphic_passport.dataframe.columns.tolist()
@ -203,9 +212,8 @@ class PlotItemGenerator:
is_last = (cur_point == len(points_pocket) - 1)
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:
if settings["force compensation FE"]:
force = point_data.useful_data["force"]
k_hardness = useful_data.k_hardness
@ -217,23 +225,23 @@ class PlotItemGenerator:
if settings["force accuracy"]:
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"]:
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)
if settings["workpiece"]:
self._add_workpiece(point_data, plot_item)
if settings["ideals"]:
self._add_ideal_stage_regions(plot_item, ideal_data, point_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_stage_regions(plot_item, ideal_data, point_data.events, pyqt_container.regions, 100)
self._add_ideal_signals(plot_item, legend, ideal_data, point_data.events, description["Ideal_signals"], pyqt_container.curves, is_last)
if settings["performance"]:
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:
@ -245,7 +253,7 @@ class PlotItemGenerator:
def _shift_data(valid_str: str,
signals: list[dict],
dataframe: pd.DataFrame,
func: function) -> pd.DataFrame:
func: Callable) -> pd.DataFrame:
keys = dataframe.keys()
for signal in signals:
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)
return dataframe
class NavigatorPlot(pg.PlotItem):
def __init__(self,
time_region:tuple[float, float],
main_plot: pg.PlotItem):
super().__init__()
self._init_navigator(time_region, main_plot)
self._init_syncranisation(main_plot)
@staticmethod
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.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,
time_region:tuple[float, float],
main_plot: pg.PlotItem) -> None:
@ -517,8 +538,4 @@ class NavigatorPlot(pg.PlotItem):
self.addItem(self.ROI_region)
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: переписать обмен данными, засунуть ссылки куда-то еще
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.setProperty("reg_items", plot_items.regions)
tab.setProperty("curve_items", plot_items.curves)

View File

@ -18,8 +18,8 @@ def main():
monitor = DirectoryMonitor()
file_manager = FileManager(monitor=monitor)
data_converter = DataConverter()
plot_widget_builder = PlotWidget()
controller = Controller(file_manager=file_manager)
plot_widget_builder = PlotWidget(controller=controller)
passport_former = PassportFormer()
window = MainWindow()
mediator = Mediator(data_converter, passport_former, plot_widget_builder, controller, file_manager)