chore: декомпозирован класс PlotWidget, нормализован обмен данными
This commit is contained in:
parent
05ee548704
commit
e4a7b39307
@ -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))
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user