python_pyqtgraph折线图工具协助分析数据
目录
写在前面:
结果显示
代码实现
导入包、字符串横坐标控件
单边折线图控件
主界面
使用过程
写在前面:
本文开发的工具主要是在平时事务处理中需要查看多列数据差异很大的数据,需要横向对比纵向对比,并且要能及时感知数据的极值、平均值、中位数等数据的位置和数据的历史变迁。就想着开发一个工具可以快速展示数据,这样能加速剖析数据并得出结论,从而缩短从原始数据到最终决策的时间。
本工具的开发宗旨是讲究一个快和便捷,所以在功能的取舍上主要以实际使用为导向
结果显示

代码实现
导入包、字符串横坐标控件
import os,sys
import pandas as pd
from typing import Dict
from PyQt5 import QtCore,QtWidgets
from PyQt5.QtCore import Qt
import pyqtgraph as pg
pg.setConfigOption('background','w')
pg.setConfigOption('foreground','k')class RotateAxisItem(pg.AxisItem):def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):p.setRenderHint(p.Antialiasing,False)p.setRenderHint(p.TextAntialiasing,True)## draw long line along axispen,p1,p2 = axisSpecp.setPen(pen)p.drawLine(p1,p2)p.translate(0.5,0) ## resolves some damn pixel ambiguity## draw ticksfor pen,p1,p2 in tickSpecs:p.setPen(pen)p.drawLine(p1,p2)## draw all text# if self.tickFont is not None:# p.setFont(self.tickFont)p.setPen(self.pen())for rect,flags,text in textSpecs:# this is the important partp.save()p.translate(rect.x(),rect.y())p.rotate(-30)p.drawText(int(-rect.width()),int(rect.height()),int(rect.width()),int(rect.height()),flags,text)# restoring the painter is *required*!!!p.restore()
单边折线图控件
class GraphWidget(QtWidgets.QWidget):def __init__(self):super().__init__()self.init_data()self.init_ui()def init_data(self):self.whole_data: Dict = Noneself.whole_xtick: list = []self.whole_x: list = []self.color_line = (30,144,255)self.cur_len = 20# 最多20条self.color_map = {'道奇蓝':(30,144,255),'橙色':(255,165,0),'深紫罗兰色':(148,0,211),'春天的绿色':(60,179,113),'热情的粉红':(255,105,180),'暗淡的灰色':(105,105,105),'番茄':(255,99,71)}self.color_16bit_map = {'道奇蓝': '#1E90FF','橙色': '#FFA500','深紫罗兰色': '#9400D3','春天的绿色': '#3CB371','热情的粉红': '#FF69B4','暗淡的灰色': '#696969','番茄': '#FF6347'}passdef init_ui(self):self.duration_label = QtWidgets.QLabel('左边界~右边界')self.left_label = QtWidgets.QLabel('左边:')self.left_slider = QtWidgets.QSlider(Qt.Horizontal)self.left_slider.valueChanged.connect(self.left_slider_valueChanged)self.right_slider = QtWidgets.QSlider(Qt.Horizontal)self.right_slider.valueChanged.connect(self.right_slider_valueChanged)self.right_label = QtWidgets.QLabel(':右边')check_btn = QtWidgets.QPushButton('确定')check_btn.clicked.connect(self.check_btn_clicked)layout_top = QtWidgets.QHBoxLayout()layout_top.addWidget(self.duration_label)layout_top.addWidget(self.left_label)layout_top.addWidget(self.left_slider)layout_top.addWidget(self.right_slider)layout_top.addWidget(self.right_label)layout_top.addWidget(check_btn)# layout_top.addStretch(1)xax = RotateAxisItem(orientation='bottom')xax.setHeight(h=50)self.pw = pg.PlotWidget(axisItems={'bottom': xax})self.pw.setMouseEnabled(x=True, y=False)# self.pw.enableAutoRange(x=False,y=True)self.pw.setAutoVisible(x=False, y=True)layout = QtWidgets.QVBoxLayout()layout.addLayout(layout_top)layout.addWidget(self.pw)self.setLayout(layout)passdef first_setData(self,data:Dict):self.whole_data = dataself.whole_x = data['x']self.whole_xtick = data['xTick']self.left_slider.setMinimum(0)self.left_slider.setMaximum(self.whole_x[-1])self.right_slider.setMinimum(0)self.right_slider.setMaximum(self.whole_x[-1])self.left_slider.setValue(0)self.right_slider.setValue(self.whole_x[-1])self.left_label.setText(f"左边:{self.whole_xtick[0]}")self.right_label.setText(f"{self.whole_xtick[-1]}:右边")self.set_data(data)passdef set_data(self,data:Dict):'''{x:[],y_list:[[],[]],y_names:[str,str],xTick00:[],xTick:[]}:param data::return:'''self.pw.clear()self.pw.addLegend()xTick = [data['xTick00']]x = data['x']y_list = data['y_list']y_names = data['y_names']self.x_Tick = data['xTick']self.y_data = y_listself.y_names = y_namesself.duration_label.setText(f"{self.x_Tick[0]}~{self.x_Tick[-1]}")xax = self.pw.getAxis('bottom')xax.setTicks(xTick)self.target_color_list = []color_keys_list = list(self.color_map.keys())for i in range(len(y_names)):t_i = i//len(color_keys_list)t_key = color_keys_list[t_i]self.target_color_list.append(t_key)self.pw.plot(x,y_list[i],connect='finite', pen=pg.mkPen({'color': self.color_map[t_key], 'width': 2}),name=y_names[i])self.vLine = pg.InfiniteLine(angle=90, movable=False)self.hLine = pg.InfiniteLine(angle=0, movable=False)self.label = pg.TextItem()self.pw.addItem(self.vLine, ignoreBounds=True)self.pw.addItem(self.hLine, ignoreBounds=True)self.pw.addItem(self.label, ignoreBounds=True)self.vb = self.pw.getViewBox()self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)# 显示整条折线图self.pw.enableAutoRange()passdef set_empty(self):self.pw.clear()passdef mouseMoved(self, evt):pos = evt[0]if self.pw.sceneBoundingRect().contains(pos):mousePoint = self.vb.mapSceneToView(pos)index = int(mousePoint.x())if index >= 0 and index < len(self.x_Tick):x_str = self.x_Tick[index]y_str_html = ''for i in range(len(self.target_color_list)):y_str = f"
{self.y_names[i]}:{self.y_data[i][index]}"y_str_html += y_strhtml_str = ' ' + x_str + ' ' + y_str_html + '
'self.label.setHtml(html_str)self.label.setPos(mousePoint.x(), mousePoint.y())self.vLine.setPos(mousePoint.x())self.hLine.setPos(mousePoint.y())passdef left_slider_valueChanged(self):left_value = self.left_slider.value()self.left_label.setText(f"左边:{self.whole_xtick[left_value]}")passdef right_slider_valueChanged(self):right_value = self.right_slider.value()self.right_label.setText(f"{self.whole_xtick[right_value]}:右边")def check_btn_clicked(self):left_value = self.left_slider.value()right_value = self.right_slider.value()if right_value<=left_value:QtWidgets.QMessageBox.information(self,'提示','左边界不能大于有边界',QtWidgets.QMessageBox.Yes)returnxTick = self.whole_data['xTick'][left_value:right_value]xTick00 = []dur_num = int(len(xTick) / float(self.cur_len))if dur_num >= 2:for i in range(0, len(xTick), dur_num):xTick00.append((i, xTick[i]))else:for i in range(0, len(xTick)):xTick00.append((i, xTick[i]))y_list00 = []y_list = self.whole_data['y_list']for item in y_list:item00 = item[left_value:right_value]y_list00.append(item00)x = [i for i in range(len(xTick))]line_data = {'xTick00': xTick00,'xTick': xTick,'x': x,'y_list': y_list00,'y_names': self.whole_data['y_names']}self.set_data(line_data)passpass
主界面
class LineMainWidget(QtWidgets.QWidget):def __init__(self):super().__init__()self.init_data()self.init_ui()passdef init_data(self):self.please_selected_str:str = '-- 请选择 --'self.field_list: list = []self.x_field: str = ''self.current_filename:str = ''self.whole_df: pd.DataFrame = Noneself.cur_len: int = 20passdef init_ui(self):self.setWindowTitle('数据折线图展示')tip_label1 = QtWidgets.QLabel('横坐标字段:')self.x_lineedit = QtWidgets.QLineEdit()self.file_name_label = QtWidgets.QLabel('文件名')self.file_name_label.setWordWrap(True)open_file_btn = QtWidgets.QPushButton('打开excel或csv文件')open_file_btn.clicked.connect(self.open_file_btn_clicked)tip_label = QtWidgets.QLabel('表头下拉列表:')self.head_combox = QtWidgets.QComboBox()self.head_combox.addItem(self.please_selected_str)self.head_combox.currentTextChanged.connect(self.head_combox_currentTextChanged)self.list_widget = QtWidgets.QListWidget()check_btn = QtWidgets.QPushButton('确定')check_btn.clicked.connect(self.check_btn_clicked)clear_btn = QtWidgets.QPushButton('清空')clear_btn.clicked.connect(self.clear_btn_clicked)layout_left = QtWidgets.QVBoxLayout()layout_left.addWidget(tip_label1)layout_left.addWidget(self.x_lineedit)layout_left.addWidget(self.file_name_label)layout_left.addWidget(open_file_btn)layout_left.addWidget(tip_label)layout_left.addWidget(self.head_combox)layout_left.addWidget(self.list_widget)layout_left.addWidget(check_btn)layout_left.addWidget(clear_btn)layout_left.addStretch(1)self.title_label = QtWidgets.QLabel('折线图标题')self.title_label.setAlignment(QtCore.Qt.AlignCenter)self.title_label.setStyleSheet('QLabel{font-size:16px;font-weight:bold}')self.line_widget = GraphWidget()layout_right = QtWidgets.QVBoxLayout()layout_right.addWidget(self.title_label)layout_right.addWidget(self.line_widget)layout = QtWidgets.QHBoxLayout()layout.addLayout(layout_left,1)layout.addLayout(layout_right,9)self.setLayout(layout)passdef open_file_btn_clicked(self):x_str = self.x_lineedit.text()x_str = x_str.strip()if len(x_str)<=0:QtWidgets.QMessageBox.information(self,'提示','请先输入横坐标字段名',QtWidgets.QMessageBox.Yes)returnpath, _ = QtWidgets.QFileDialog.getOpenFileName(self,'打开Excel文件或csv文件','.','Excel或CSV(*.xlsx *.csv)')if not path:returnif path.endswith('.xlsx'):df = pd.read_excel(path,engine='openpyxl')passelif path.endswith('.csv'):df = pd.read_csv(path,encoding='utf-8')passelse:QtWidgets.QMessageBox.information(self,'提示','只能上传Excel文件或CSV文件',QtWidgets.QMessageBox.Yes)returnself.file_name_label.setText(path)self.field_list.clear()cols = df.columnsif x_str not in cols:QtWidgets.QMessageBox.information(self,'提示','横坐标字段不在文件中',QtWidgets.QMessageBox.Yes)returnfor col in cols:if str(df[col].dtype)=='object':continueself.field_list.append(col)self.x_field = x_strself.current_filename = os.path.basename(path)self.whole_df = df.copy()self.head_combox.clear()self.head_combox.addItem(self.please_selected_str)self.head_combox.addItems(self.field_list)passdef head_combox_currentTextChanged(self,txt):cur_txt = self.head_combox.currentText()if len(cur_txt.strip())<=0:returnif cur_txt == self.please_selected_str:returnself.list_widget.addItem(cur_txt)passdef check_btn_clicked(self):total_count = self.list_widget.count()if total_count > 20 or total_count<=0:QtWidgets.QMessageBox.information(self,'提示','选择的字段在1个到20个之间',QtWidgets.QMessageBox.Yes)returnselected_list = []for i in range(total_count):item = self.list_widget.item(i)selected_list.append(item.text())df = self.whole_df.copy()xTick = df[self.x_field].values.tolist()xTick00 = []dur_num = int(len(xTick) / float(self.cur_len))if dur_num >= 2:for i in range(0, len(xTick), dur_num):xTick00.append((i, xTick[i]))else:for i in range(0, len(xTick)):xTick00.append((i, xTick[i]))y_list = []for item in selected_list:y_one = df[item].values.tolist()y_list.append(y_one)if total_count<=1:title_str = f"{self.current_filename}_{selected_list[0]}"else:title_str = f"{self.current_filename}_多列"line_data = {'xTick00': xTick00,'xTick': xTick,'x': [i for i in range(0, len(df))],'y_list': y_list,'y_names': selected_list}self.title_label.setText(title_str)self.line_widget.first_setData(line_data)passdef clear_btn_clicked(self):self.list_widget.clear()
使用过程
if __name__ == '__main__':QtCore.QCoreApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)app = QtWidgets.QApplication(sys.argv)main_window = LineMainWidget()main_window.showMaximized()app.exec()pass

1 输入作为横坐标的字段
2 选择要显示的文件
3 选择要显示的字段,可以选择多个
4 点击“确定”后,右侧就会画出折线图
5 移动滑块,可以改变横坐标区间
6 点击“确定”,折线图就会显示当前选定的区间
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
