python - 无法同时在多个 pyqtgraph 图形上显示鼠标位置标记
问题描述
TIA。
github:https ://github.com/b3st0fth3w0rst/data_plotter
我在使 x 轴(垂直线)标记出现在所有 3 个图形上时遇到了一些问题,指示鼠标当前的位置。最初我希望这些图表彼此独立,但现在不是那么多了。
我想做的是:
当鼠标在 3 个图形中的任何一个中移动时,垂直 x 轴线同时在所有 3 个图形上跟随鼠标。目前它们是独立的,我已经设置了它,以便只有“点击/聚焦”图表显示运动。
当鼠标在 3 个图表中的任何一个中移动时,水平 y 轴线应该只在当前“点击/聚焦”图表中跟随鼠标,就像我现在设置它的方式一样(鼠标标记只显示焦点图表中的移动)。
笔记:
- 单击“加载数据”以加载 csv 文件。
- 选择要在图表上绘制的复选框项目。
- 单击 3 个图表中的任何一个,开始在那里绘制 cb 项目。
我对使用 python 构建 GUI 应用程序很陌生,我觉得我错过了一些简单的东西。我尝试在所有 3 个图表上执行 mouseMoved() 代码,特别是在我设置 v/hLine 的地方,但这似乎只在最后一个图表中激活鼠标标记(参见第二次提交。第一次提交是我的初始工作设计)。
test_main.py
import csv
import os
import pandas as pd
import pyqtgraph as pg
import sys
from test_plot import Plot
from PyQt5 import QtWidgets, QtCore, QtGui
from ui.test_window import Ui_MainWindow
CURRENT_FOCUSED_PLOT_ID = None
PREVIOUS_FOCUSED_PLOT_ID = None
CURRENT_EVENT = None
PLOT_EVENT_MAP = {}
class Connector:
"""
Class that connects the compiled test_window.py to test_main.py.
This class contains the modified code that does not get overridden by mod.py.
"""
class MyPlotWidget(pg.PlotWidget):
"""
Overridden PlotWidget class to add a mouseclick handler to each of the graphs created.
"""
def __init__(self, connector):
super().__init__()
self.scene().sigMouseClicked.connect(self.mouse_clicked_event_handler)
self.main_myplotwidget = connector
def mouse_clicked_event_handler(self):
"""
Mouse clicked event handler for graphs
"""
global CURRENT_FOCUSED_PLOT_ID
CURRENT_FOCUSED_PLOT_ID = id(self)
if CURRENT_FOCUSED_PLOT_ID != PREVIOUS_FOCUSED_PLOT_ID:
self.main_myplotwidget.update_graphs_checkboxes()
mouse_point_x = self.main_myplotwidget.current_mouse_point[0]
def mouseMoveEvent(self, ev):
"""
Mouse movement on graph event handler
:param ev: QMouseEvent - Event object for the graphs
"""
try:
# if id(self) == CURRENT_FOCUSED_PLOT_ID:
mouse_point = self.main_myplotwidget.plot.mouse_moved(ev,
self.main_myplotwidget.get_current_graph(),
self.main_myplotwidget.plot_widgets)
if 0 <= mouse_point[0] <= self.main_myplotwidget.x_range \
and 0 <= mouse_point[1] <= self.main_myplotwidget.y_range:
self.main_myplotwidget.ui.mouse_point_info_label.setText('Mouse Point: <span '
'style="font-size:10pt" '
'style="color:red">X={}</span>, Y={}'
.format(mouse_point[0], mouse_point[1])
)
self.main_myplotwidget.current_mouse_point = mouse_point
except Exception as e:
print(e)
def __init__(self):
self.model = QtGui.QStandardItemModel()
self.app = QtWidgets.QApplication(sys.argv)
self.main_window = QtWidgets.QMainWindow()
self.ui = Ui_MainWindow(self.main_window)
self.csv_data = {}
self.column_name_cb = {}
self.graph_cb_data = {}
self.plot_widgets = []
self.x_range = 0
self.y_range = 0
self.current_mouse_point = (0, 0)
self.ui_setup()
self.plot = Plot(self.ui.mouse_point_info_label)
def ui_setup(self):
"""
Setups up the UI with additional/modified parameters that may or may not be available in test_window.py
"""
self.ui.load_data_pb.clicked.connect(self.load_data)
self.ui.plot_widget_1 = self.MyPlotWidget(self)
self.ui.plot_widget_1.setFocusPolicy(QtCore.Qt.ClickFocus)
self.ui.plot_widget_1.setStyleSheet("background-color: rgb(0, 0, 0);")
self.ui.plot_widget_1.setObjectName("plot_widget_1")
self.ui.gridLayout.addWidget(self.ui.plot_widget_1, 0, 0, 1, 1)
self.ui.plot_widget_2 = self.MyPlotWidget(self)
self.ui.plot_widget_2.setFocusPolicy(QtCore.Qt.ClickFocus)
self.ui.plot_widget_2.setStyleSheet("background-color: rgb(255, 255, 0);")
self.ui.plot_widget_2.setObjectName("plot_widget_2")
self.ui.gridLayout_7.addWidget(self.ui.plot_widget_2, 0, 0, 1, 1)
self.ui.plot_widget_3 = self.MyPlotWidget(self)
self.ui.plot_widget_3.setFocusPolicy(QtCore.Qt.ClickFocus)
self.ui.plot_widget_3.setStyleSheet("background-color: rgb(0, 85, 255);")
self.ui.plot_widget_3.setObjectName("plot_widget_3")
self.ui.gridLayout_8.addWidget(self.ui.plot_widget_3, 0, 0, 1, 1)
self.plot_widgets.append(self.ui.plot_widget_1)
self.plot_widgets.append(self.ui.plot_widget_2)
self.plot_widgets.append(self.ui.plot_widget_3)
# Set the default graph focused to the top graph
global CURRENT_FOCUSED_PLOT_ID
CURRENT_FOCUSED_PLOT_ID = id(self.ui.plot_widget_1)
global PREVIOUS_FOCUSED_PLOT_ID
PREVIOUS_FOCUSED_PLOT_ID = CURRENT_FOCUSED_PLOT_ID
def get_current_graph(self):
"""
Returns the currently focused graph object.
:return: MyPlotWidget - The currently focused plot.
"""
for graph in self.plot_widgets:
if CURRENT_FOCUSED_PLOT_ID == id(graph):
return graph
def plot_data(self, graph, data):
"""
Plots the given data on the given graph.
:param graph: MyPlotWidget - PlotWidget object to plot data on.
:param data: Dict - Dictionary containing the data name and data set.
"""
self.plot.plot_values(graph, data, self.x_range, self.y_range, self.plot_widgets)
def load_data(self, file_path=None):
"""
Handles opening a CSV file
"""
# if not file_path:
# csv_file_name, _ = QFileDialog.getOpenFileName(self.main_window)
# else:
# csv_file_name = file_path
csv_file_name = 'resource\\test_data.csv'
if os.path.exists(csv_file_name):
for plot in self.plot_widgets:
plot.clear()
with open(csv_file_name, "r") as f:
csv_reader = csv.reader(f, delimiter=',')
columns = next(csv_reader)
df = pd.read_csv(csv_file_name, usecols=columns)
for item in columns:
if item != 'Name':
self.csv_data[item] = df[item]
for row in csv.reader(f):
items = [QtGui.QStandardItem(field) for field in row]
self.model.appendRow(items)
# Create Column checkboxes
for column in self.csv_data.keys():
self.column_name_cb['check_box_{}'.format(column)] = QtWidgets.QCheckBox(
self.ui.scrollAreaWidgetContents)
self.column_name_cb.get('check_box_{}'.format(column)).setObjectName(
"check_box_{}".format(column))
self.column_name_cb.get('check_box_{}'.format(column)).setText(column)
self.column_name_cb.get('check_box_{}'.format(column)).stateChanged.connect(
self.checkbox_state_changed_csv)
self.ui.verticalLayout_3.addWidget(self.column_name_cb.get('check_box_{}'.format(column)))
self.x_range = len(self.csv_data.get(list(self.csv_data.keys())[0]))
self.y_range = 150
self.ui.plot_widget_1.setEnabled(True)
self.ui.plot_widget_2.setEnabled(True)
self.ui.plot_widget_3.setEnabled(True)
def checkbox_state_changed_csv(self):
"""
Handles when the checkboxes state change for the Pot tab.
"""
columns_to_plot = []
data_to_plot = {}
for checkbox in self.column_name_cb.items():
if checkbox[1].isChecked():
columns_to_plot.append(checkbox[0])
for item in columns_to_plot:
item = item.split('_')[-1]
data_to_plot[item] = self.csv_data.get(item).values
if PREVIOUS_FOCUSED_PLOT_ID == CURRENT_FOCUSED_PLOT_ID and data_to_plot:
self.graph_cb_data[CURRENT_FOCUSED_PLOT_ID] = data_to_plot
self.plot_data(self.get_current_graph(), data_to_plot)
elif PREVIOUS_FOCUSED_PLOT_ID != CURRENT_FOCUSED_PLOT_ID:
self.plot_data(self.get_current_graph(), data_to_plot)
else:
self.plot.clear_plot_widget(self.get_current_graph())
def update_graphs_checkboxes(self):
"""
Updates the checkboxes in the CSV tab to match the currently focused graph.
A mouse click on any particular graph will show the associated checkboxes.
"""
for checkbox in self.column_name_cb.items():
checkbox[1].setChecked(False)
global PREVIOUS_FOCUSED_PLOT_ID
if PREVIOUS_FOCUSED_PLOT_ID != CURRENT_FOCUSED_PLOT_ID and \
CURRENT_FOCUSED_PLOT_ID not in self.graph_cb_data.keys():
self.plot_data(self.get_current_graph(), {})
for cb_data in self.graph_cb_data.items():
if cb_data[0] == id(self.get_current_graph()):
data_list = list(cb_data[1].keys())
for checkbox in self.column_name_cb.items():
if checkbox[0].split('_')[-1] in data_list:
checkbox[1].setChecked(True)
else:
checkbox[1].setChecked(False)
PREVIOUS_FOCUSED_PLOT_ID = CURRENT_FOCUSED_PLOT_ID
if __name__ == "__main__":
a = Connector()
a.main_window.show()
sys.exit(a.app.exec_())
test_plot.py
import numpy as np
import random
import pyqtgraph as pg
from PyQt5 import QtCore
class Plot:
def __init__(self, mouse_point_info_label):
self.widget = None
self.data = None
self.x_range = None
self.y_range = None
self.color_map = []
self.vb = []
self.hLine = None
self.vLine = None
self.event_graph_map = {}
self.mouse_point_info_label = mouse_point_info_label
def get_color(self, item_to_color):
"""
Returns a random color.
:param item_to_color: Specific data item to color. self.color_map keep track of colors used for items. If an
item has already been colored we'll reuse the same color, otherwise pick a new color.
:return: Tuple - RGB values representing a color (255,255,255).
"""
color = (255, 255, 255)
for item in self.data.items():
if item[0] == item_to_color:
existing_color = [i for i, v in enumerate(self.color_map) if v[0] == item[0]]
if existing_color is not None and existing_color:
return self.color_map[existing_color[0]][1]
else:
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
color = (r, g, b)
self.color_map.append((item[0], color))
return color
self.color_map.append((item_to_color, color))
return color
def mouse_moved(self, evt, current_graph, graph_objs, x=None, y=None):
self.widget = current_graph
mouse_point_x = 0
mouse_point_y = 0
if x is not None:
self.vLine.setPos(x)
if y is not None:
self.hLine.setPos(y)
else:
self.hLine.setPos(0)
return x, y
else:
x = evt.x()
y = evt.y()
pos = QtCore.QPointF(x, y)
for item in self.vb:
# if self.widget.sceneBoundingRect().contains(pos):
try:
mouse_point = item[1].mapSceneToView(pos)
mouse_point_x = int(mouse_point.x())
mouse_point_y = int(mouse_point.y())
# if 0 <= mouse_point_x <= self.x_range and 0 <= mouse_point_y <= self.y_range:
self.vLine.setPos(mouse_point.x())
self.hLine.setPos(mouse_point.y())
except Exception as e:
pass
return mouse_point_x, mouse_point_y
def plot_values(self, plot_widget, data, x_range, y_range, graph_objs):
"""
Plots the given values on the given graph.
:param plot_widget: MyPlotWidget - Overridden PlotWidget method in main.Connector() class. Graph object.
:param data: Dict - Containing the name of the data to plot and the data itself.
:param x_range: Int - Max x-axis value.
:param y_range: Int - Max y-axis value.
"""
self.widget = plot_widget
self.data = data
# Setup the 3 graphs, but only plot the data on the focused graph
for item in graph_objs:
self.x_range = x_range
self.y_range = y_range
self.widget.setXRange(0, self.x_range)
self.widget.setYRange(0, self.y_range)
self.widget.showGrid(x=True, y=True)
# self.widget.setLabel('left', 'Value', units='y')
self.widget.setLabel('bottom', 'Frames')
self.widget.addLegend()
self.widget.clear()
for item in self.data.items():
line = self.widget.plot(np.insert(item[1], 0, item[1][0]), pen=self.get_color(item[0]),
symbolPen=self.get_color(item[0]), symbol='o', symbolSize=1, name=item[0])
self.marker(graph_objs)
def marker(self, graph_objs):
# Paint the marker for all 3 graphs and add the mouseMoved signal
for item in graph_objs:
# Crosshair
self.vLine = pg.InfiniteLine(angle=90, movable=False)
self.hLine = pg.InfiniteLine(angle=0, movable=False)
item.addItem(self.vLine, ignoreBounds=True)
item.addItem(self.hLine, ignoreBounds=True)
self.vb.append((item, item.plotItem.vb))
proxy = pg.SignalProxy(item.scene().sigMouseMoved, rateLimit=60, slot=self.mouse_moved)
item.scene().sigMouseMoved.connect(self.mouse_moved)
def clear_plot_widget(self, plot_widget):
plot_widget.clear()
解决方案
推荐阅读
- javascript - 由合并面构成的剪裁几何体,模板盖面未正确对齐
- c++ - 如何在 gtkmm 的信号处理程序中获取信号小部件?
- c# - 锁对象数组可用于保证对 MyClassArray 的线程安全访问?
- emacs - 从 org-capture 中获取当前缓冲区目录
- google-maps - 用于导航自定义路线的 Google Maps API
- python - 使用python将数据框转换为json
- python - Pygame图像加载找不到图像
- amazon-mws - 有没有办法使用供应商帐户注册为亚马逊 SP-API 的开发人员?
- c# - 如何最好地存储 .NET 控制台应用程序的命令及其响应代码?
- performance - 为可变复杂性任务或可变速度节点负载平衡 MPI 多线程?