from PySide6.QtWidgets import QApplication, QMainWindow, QSlider, QVBoxLayout, QComboBox, QWidget, QPushButton, QToolBar, QGridLayout, QHBoxLayout, QLabel, QRadioButton, QScrollArea, QSizePolicy, QSpacerItem from PySide6.QtCore import Qt, QTimer, QSize, QPointF from PySide6.QtCharts import QChart, QChartView, QLineSeries, QValueAxis from PySide6.QtGui import QAction, QPainter, QColor, QIcon, QFont from codes.common import clibs import os import pandas class ChartWindow(QMainWindow): curve_map = { "周期内平均转矩": ["device_servo_trq_feedback", ], "周期内最大速度": ["hw_joint_vel_feedback", ], } ylabels = {"周期内最大速度": "速度(rad/s)", "周期内平均转矩": "转矩(Nm)"} colors = [QColor(255, 0, 0), QColor(255, 0, 0), QColor(0, 0, 255), QColor(0, 255, 0), QColor(254, 153, 0), QColor(0, 255, 255), QColor(255, 0, 255), QColor(0, 0, 0), QColor(255, 255, 255), QColor(128, 128, 128), QColor(255, 182, 193), QColor(173, 216, 230), QColor(144, 238, 144)] def __init__(self, dir_path, procs): super().__init__() self.procs = procs self.dir_path = dir_path self.setup_ui() self.detect_files() # self.timer = QTimer() # self.timer.timeout.connect(self.reset_zoom) # self.timer.start(60000) # 60000ms 更新一次数据 def detect_files(self): with open(f"{clibs.PREFIX}/qss/style.qss", mode="r", encoding="utf-8") as f_qss: style_sheet = f_qss.read() self.setStyleSheet(style_sheet) if not os.path.exists(self.dir_path): clibs.logger("ERROR", "curves_draw", f"数据文件夹{self.dir_path}不存在,请确认后重试......", "red") for proc_name, is_enabled in self.procs.items(): if is_enabled: break else: clibs.logger("ERROR", "curves_draw", f"需要选择至少一个功能,才能继续绘图......", "red") _, files = clibs.traversal_files(self.dir_path) csv_files = [] for file in files: if file.lower().endswith(".csv"): self.cb_func_name.addItem(file.split("/")[-1].split(".")[0]) csv_files.append(file) if len(csv_files) == 0: clibs.logger("ERROR", "curves_draw", "程序未开始运行,暂无数据可以展示......", "red") self.reset_zoom() def get_current_info(self): self.func_name = self.cb_func_name.currentText() self.file_name = f"{self.dir_path}/{self.func_name}.csv" self.df = pandas.read_csv(self.file_name) self.horizontal_slider.setRange(0, len(self.df)) def center_on_screen(self): screen_geometry = QApplication.primaryScreen().geometry() screen_width = screen_geometry.width() screen_height = screen_geometry.height() win_width = self.width() win_height = self.height() x = (screen_width - win_width) // 2 y = (screen_height - win_height) // 2 self.move(x, y) def setup_ui(self): self.setWindowTitle("曲线绘制") self.setGeometry(100, 100, 1000, 500) self.setWindowIcon(QIcon(f"{clibs.PREFIX}/media/icon.ico")) self.center_on_screen() # 窗体布局 self.centralwidget = QWidget(self) self.centralwidget.setObjectName(u"centralwidget") self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout.setObjectName(u"gridLayout") # 布局spinbox 和 label coordinate self.hl_spinbox_btn = QHBoxLayout() self.hl_spinbox_btn.setObjectName(u"hl_spinbox_btn") font = QFont() font.setFamilies([u"Consolas"]) font.setPointSize(10) self.rb_x = QRadioButton(self.centralwidget) self.rb_x.setObjectName(u"rb_x") self.rb_x.setText(u"仅X轴缩放") self.rb_x.setFont(font) self.rb_y = QRadioButton(self.centralwidget) self.rb_y.setObjectName(u"rb_y") self.rb_y.setText(u"仅Y轴缩放") self.rb_y.setFont(font) self.rb_xy = QRadioButton(self.centralwidget) self.rb_xy.setObjectName(u"rb_xy") self.rb_xy.setText(u"XY轴缩放") self.rb_xy.setFont(font) self.rb_xy.setChecked(True) self.cb_func_name = QComboBox(self.centralwidget) self.cb_func_name.setObjectName(u"cb_func_name") self.cb_func_name.setMinimumSize(QSize(200, 0)) self.cb_func_name.setFont(font) self.button_reset = QPushButton(self.centralwidget) self.button_reset.setObjectName(u"button_reset") self.button_reset.setFont(font) self.button_reset.setText(u"视图重置") self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.hl_spinbox_btn.addWidget(self.rb_x) self.hl_spinbox_btn.addWidget(self.rb_y) self.hl_spinbox_btn.addWidget(self.rb_xy) self.hl_spinbox_btn.addItem(self.horizontalSpacer) self.hl_spinbox_btn.addWidget(self.cb_func_name) self.hl_spinbox_btn.addWidget(self.button_reset) # 连接信号 self.rb_x.clicked.connect(lambda: self.set_zoom_mode(QChartView.RubberBand.HorizontalRubberBand)) self.rb_y.clicked.connect(lambda: self.set_zoom_mode(QChartView.RubberBand.VerticalRubberBand)) self.rb_xy.clicked.connect(lambda: self.set_zoom_mode(QChartView.RubberBand.RectangleRubberBand)) self.button_reset.clicked.connect(self.reset_zoom) self.cb_func_name.currentTextChanged.connect(self.cb_func_name_change) # 生成曲线图 self.chart = QChart() self.chart.setTheme(QChart.ChartTheme.ChartThemeLight) # 设置主题 self.chart.legend().setVisible(True) self.chart.legend().setAlignment(Qt.AlignmentFlag.AlignRight) # 图例右对齐 self.chart_view = QChartView(self.chart) self.chart_view.setRubberBand(QChartView.RubberBand.RectangleRubberBand) # 矩形缩放 self.chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) # 抗锯齿 self.axisX = QValueAxis() # self.axisX.setTitleText("X轴") self.axisX.setRange(0, 10) # 初始显示范围 self.axisY = QValueAxis() # self.axisY.setTitleText("Y轴") self.axisY.setRange(-5, 5) self.chart.addAxis(self.axisX, Qt.AlignmentFlag.AlignBottom) self.chart.addAxis(self.axisY, Qt.AlignmentFlag.AlignLeft) # 创建水平滚动条 self.horizontal_slider = QSlider(Qt.Orientation.Horizontal) self.horizontal_slider.valueChanged.connect(self.update_view) # self.horizontal_slider.setFixedHeight(200) # 左侧布局 self.vl_central = QVBoxLayout() self.vl_central.setObjectName(u"vl_central") self.vl_central.addWidget(self.chart_view) self.vl_central.addWidget(self.horizontal_slider) self.vl_central.addLayout(self.hl_spinbox_btn) self.vl_central.setStretch(0, 20) self.vl_central.setStretch(1, 1) self.vl_central.setStretch(2, 1) self.gridLayout.addLayout(self.vl_central, 0, 0) self.setCentralWidget(self.centralwidget) def connect_legend_markers(self): for marker in self.chart.legend().markers(): try: marker.clicked.disconnect() except: pass marker.clicked.connect(self.toggle_series_visibility) self.update_legend_style(marker) def update_legend_style(self, marker): alpha = 255 if marker.series().isVisible() else 128 color = marker.labelBrush().color() color.setAlpha(alpha) marker.setLabelBrush(color) marker_color = marker.series().color() marker_color.setAlpha(alpha) marker.setBrush(marker_color) marker.setPen(marker_color.darker()) def toggle_series_visibility(self): marker = self.sender() if series := marker.series(): new_visibility = not series.isVisible() series.setVisible(new_visibility) self.update_legend_style(marker) marker.setVisible(True) def cb_func_name_change(self): self.reset_zoom() def set_zoom_mode(self, mode): """设置缩放模式""" self.chart_view.setRubberBand(mode) # 取消其他按钮的选中状态 self.rb_x.setChecked(mode == QChartView.RubberBand.HorizontalRubberBand) self.rb_y.setChecked(mode == QChartView.RubberBand.VerticalRubberBand) self.rb_xy.setChecked(mode == QChartView.RubberBand.RectangleRubberBand) def reset_zoom(self): self.chart.removeAllSeries() self.get_current_info() self.axisY.setTitleText(self.ylabels[self.func_name]) series_s = [QLineSeries() for _ in range(18)] for col in range(1, len(self.df.columns)): self.series = series_s[col] self.chart.addSeries(self.series) self.series.attachAxis(self.axisX) self.series.attachAxis(self.axisY) data = [QPointF(idx, self.df.iloc[:, col].values[idx]) for idx in range(len(self.df))] self.series.append(data) self.series.setColor(self.colors[col]) self.series.setName(self.df.columns[col]) self.chart.zoomReset() # 重置所有缩放 self.horizontal_slider.setValue(0) self.axisX.setRange(0, len(self.df)) self.axisY.setRange(min(self.df.iloc[:, 1:].min())-1, max(self.df.iloc[:, 1:].max())+1) self.connect_legend_markers() def update_view(self): self.connect_legend_markers() # 自动滚动显示最新10个点 if self.horizontal_slider.value() > 10: self.axisX.setRange(self.horizontal_slider.value() - 10, self.horizontal_slider.value()) else: self.axisX.setRange(0, 10)