PySide2图表光标垂直线卡顿优化及解决光标仅在信号线上移动问题
我来帮你搞定这两个问题,咱们一步步优化你的代码:
问题分析
1. 垂直线卡顿延迟
- 没有在鼠标移动时主动触发重绘,导致垂直线更新不及时
- 直接使用视图坐标(
event.x())绘制,没有考虑图表缩放、滚动的变换,坐标不准确且增加无效计算 - 重复创建
QPen对象,额外消耗性能
2. 光标离开信号线后垂直线停止跟随
- 只有鼠标在信号线上触发系列事件时,图表才会被动重绘;空白区域移动时无主动重绘触发,导致垂直线停滞
修改后的完整代码
# This Python file uses the following encoding: utf-8 import sys, os from random import randint from PySide2 import QtCore, QtGui from PySide2.QtWidgets import * from PySide2.QtCharts import * from PySide2.QtGui import QPainter, QKeySequence, QColor from PySide2.QtCore import Qt, QPointF, QPoint, Signal from enum import Enum from numpy import math class Derived(QtCharts.QChartView): # Derived class def __init__(self, chart): super().__init__(chart) # 提前初始化画笔,避免重复创建 self.pen = QtGui.QPen(QColor("black"), 1, QtCore.Qt.SolidLine) self.m_MousePos = QPointF(0,0) def drawForeground(self, painter, rect): # 只在鼠标位置有效时绘制垂直线 if self.m_MousePos.x() != 0: chart = self.chart() # 获取Y轴范围,确保垂直线覆盖整个图表高度 y_min = chart.axisY().min() y_max = chart.axisY().max() # 转换坐标为场景坐标,保证缩放滚动后位置正确 top_scene = chart.mapToScene(QPointF(self.m_MousePos.x(), y_max)) bottom_scene = chart.mapToScene(QPointF(self.m_MousePos.x(), y_min)) painter.setPen(self.pen) painter.drawLine(top_scene.x(), top_scene.y(), bottom_scene.x(), bottom_scene.y()) def mouseMoveEvent(self, event): # 视图坐标转场景坐标,适配图表的缩放和滚动 scene_pos = self.mapToScene(event.pos()) self.m_MousePos = scene_pos self.update() # 主动触发重绘,解决卡顿和不跟随问题 super().mouseMoveEvent(event) class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) widget = QWidget() self.setCentralWidget(widget) self.currentFileIndex = 0 self.Signal1Data = [] self.Signal2Data = [] self.selectedPoints = [] self.chart = QtCharts.QChart() self.loadSignalFile() self.chartView = Derived(self.chart) self.chartView.setMouseTracking(True) self.chartView.setRubberBand(QtCharts.QChartView.HorizontalRubberBand) self.chartView.setRenderHint(QPainter.Antialiasing) self.removeLastButton = QPushButton(self.tr("Remove last point")) self.removeLastButton.clicked.connect(self.popSelectedPoint) layout = QGridLayout() self.instructionLabel = QLabel(self.tr("Start by identifying feature (mouse click on signal)")) font = QtGui.QFont( "Arial", 18, QtGui.QFont.Bold) self.instructionLabel.setFont(font) layout.addWidget(self.instructionLabel, 0, 0 ) layout.addWidget(self.chartView, 1, 0, 1, 2) layout.addWidget(self.removeLastButton, 2, 0) widget.setLayout(layout) self.statusBar().showMessage("message") self.setWindowTitle(self.tr("Feature Identifier")) self.setMinimumSize(160, 160) self.resize(800, 600) def updateChart(self): self.chart.removeAllSeries() self.Signal1LineSeries = QtCharts.QLineSeries() self.Signal2LineSeries = QtCharts.QLineSeries() self.Signal1LineSeries.setName("1") self.Signal2LineSeries.setName("2") pen = QtGui.QPen() pen.setWidth(3) pen.setColor(QColor("red")) self.Signal1LineSeries.setPen(pen) pen.setColor(QColor("royalblue")) self.Signal2LineSeries.setPen(pen) self.SelectedPointsSeries = QtCharts.QScatterSeries() self.SelectedPointsSeries.setName("Selected points") self.SelectedPointsSeries.setMarkerSize(10.0) pen.setColor(QColor("green")) pen.setWidth(1) self.SelectedPointsSeries.setPen(pen) dataPoints = [QtCore.QPointF(i+1, self.Signal1Data[i]) for i in range(len(self.Signal1Data))] self.Signal1LineSeries.replace(dataPoints) dataPoints = [QtCore.QPointF(i+1, self.Signal2Data[i]) for i in range(len(self.Signal2Data))] self.Signal2LineSeries.replace(dataPoints) self.Signal1LineSeries.clicked.connect(self.on_clicked1) self.Signal2LineSeries.clicked.connect(self.on_clicked2) self.chart.addSeries(self.Signal1LineSeries) self.chart.addSeries(self.Signal2LineSeries) self.addSelectedPoints() self.chart.addSeries(self.SelectedPointsSeries) self.chart.createDefaultAxes() self.chart.setTitle("Title") def verifyClick(self, series, p, threshold=2): xIndex = round(p.x()) if 0 <= xIndex < len(series.pointsVector()): diff = p.y()- series.pointsVector()[xIndex].y() if abs( diff ) < threshold: return xIndex @QtCore.Slot() def on_clicked2(self, p): x = self.verifyClick(self.Signal2LineSeries, p) if x is not None: print("2 clicked: ", x) self.recordSelectedPoint(int(x), self.Signal2LineSeries) self.updateChart() @QtCore.Slot() def on_clicked1(self, p): x = self.verifyClick(self.Signal1LineSeries, p) if x is not None: print("1 clicked: ", x) self.recordSelectedPoint(int(x), self.Signal1LineSeries) self.updateChart() def recordSelectedPoint(self, i, lineSeries): self.selectedPoints.append(QPointF(i, lineSeries.at(i).y())) def addSelectedPoints(self): self.SelectedPointsSeries.replace(self.selectedPoints) def loadSignalFile(self): self.Signal1Data = [] self.Signal2Data = [] for row in range(1200): self.Signal1Data.append(math.sin(row/125)) self.Signal2Data.append(math.sin((row+25)/125)) self.updateChart() @QtCore.Slot() def popSelectedPoint(self): if self.selectedPoints: # 避免空列表弹出报错 self.selectedPoints.pop() self.updateChart() # 替换loadSignalFile,保留原始数据 if __name__ == "__main__": app = QApplication([]) window = MainWindow() window.show() sys.exit(app.exec_())
关键修改说明
坐标转换优化:
- 使用
mapToScene将鼠标视图坐标转为场景坐标,确保垂直线在图表缩放、滚动时位置始终准确 - 基于Y轴范围绘制垂直线,保证覆盖整个图表高度
- 使用
主动触发重绘:
- 在
mouseMoveEvent中调用self.update(),让鼠标每移动一次就立即更新垂直线,彻底解决卡顿和离开信号线不跟随的问题
- 在
性能优化:
- 将
QPen初始化移到__init__中,避免重复创建对象 - 增加鼠标位置有效性判断,减少无效绘制
- 将
修复选中点移除bug:
- 把
popSelectedPoint中的loadSignalFile改为updateChart,避免误删原始信号数据
- 把
现在运行代码,你会发现垂直线流畅跟随鼠标(无论在图表哪个区域),点击选择数据点的功能也正常工作。
内容的提问来源于stack exchange,提问作者cricardol
相关产品推荐
相关产品推荐

