python_pyqtgraph折线图工具协助分析数据

news2024/12/23 14:10:16

目录

写在前面:

结果显示

 代码实现

导入包、字符串横坐标控件

单边折线图控件

主界面

使用过程


写在前面:

本文开发的工具主要是在平时事务处理中需要查看多列数据差异很大的数据,需要横向对比纵向对比,并且要能及时感知数据的极值、平均值、中位数等数据的位置和数据的历史变迁。就想着开发一个工具可以快速展示数据,这样能加速剖析数据并得出结论,从而缩短从原始数据到最终决策的时间。

本工具的开发宗旨是讲究一个快和便捷,所以在功能的取舍上主要以实际使用为导向

结果显示

 代码实现

导入包、字符串横坐标控件

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 axis
        pen,p1,p2 = axisSpec
        p.setPen(pen)
        p.drawLine(p1,p2)
        p.translate(0.5,0)  ## resolves some damn pixel ambiguity

        ## draw ticks
        for 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 part
            p.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 = None
        self.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'
        }
        pass
    def 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)
        pass

    def first_setData(self,data:Dict):
        self.whole_data = data
        self.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)
        pass

    def 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_list
        self.y_names = y_names

        self.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()
        pass

    def set_empty(self):
        self.pw.clear()
        pass

    def 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"<br><font color='{self.color_16bit_map[self.target_color_list[i]]}'>{self.y_names[i]}:{self.y_data[i][index]}</font>"
                    y_str_html += y_str

                html_str = '<p style="color:black;font-size:18px;font-weight:bold;">&nbsp;' + x_str + '&nbsp;' + y_str_html + '</p>'
                self.label.setHtml(html_str)
                self.label.setPos(mousePoint.x(), mousePoint.y())
            self.vLine.setPos(mousePoint.x())
            self.hLine.setPos(mousePoint.y())
        pass

    def left_slider_valueChanged(self):
        left_value = self.left_slider.value()
        self.left_label.setText(f"左边:{self.whole_xtick[left_value]}")
        pass
    def 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
            )
            return
        xTick = 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)
        pass
    pass

主界面

class LineMainWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.init_data()
        self.init_ui()
        pass
    def 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 = None
        self.cur_len: int = 20
        pass
    def 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)
        pass
    def 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
            )
            return
        path, _ = QtWidgets.QFileDialog.getOpenFileName(
            self,
            '打开Excel文件或csv文件',
            '.',
            'Excel或CSV(*.xlsx *.csv)'
        )
        if not path:
            return
        if path.endswith('.xlsx'):
            df = pd.read_excel(path,engine='openpyxl')
            pass
        elif path.endswith('.csv'):
            df = pd.read_csv(path,encoding='utf-8')
            pass
        else:
            QtWidgets.QMessageBox.information(
                self,
                '提示',
                '只能上传Excel文件或CSV文件',
                QtWidgets.QMessageBox.Yes
            )
            return
        self.file_name_label.setText(path)
        self.field_list.clear()
        cols = df.columns
        if x_str not in cols:
            QtWidgets.QMessageBox.information(
                self,
                '提示',
                '横坐标字段不在文件中',
                QtWidgets.QMessageBox.Yes
            )
            return
        for col in cols:
            if str(df[col].dtype)=='object':
                continue
            self.field_list.append(col)
        self.x_field = x_str
        self.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)
        pass
    def head_combox_currentTextChanged(self,txt):
        cur_txt = self.head_combox.currentText()
        if len(cur_txt.strip())<=0:
            return
        if cur_txt == self.please_selected_str:
            return
        self.list_widget.addItem(cur_txt)
        pass
    def 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
            )
            return

        selected_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)

        pass
    def 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 点击“确定”,折线图就会显示当前选定的区间 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/748681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux和Shell笔记-1相关概念理解

Unix和Linux关系 UNIX是最早的商业操作系统之一&#xff0c;由贝尔实验室&#xff08;AT&T Bell Laboratories&#xff09;于 1970 年代开发。UNIX 是一个多用户、多任务的操作系统&#xff0c;具有强大的命令行界面和可扩展性。 Linux 是一个开放源代码的类 UNIX 操作系统…

屏蔽表面电阻试验仪

一、产品概述 KDZD608A屏蔽服效率测试装置是对屏蔽服进行屏蔽效率测试的专业设备&#xff0c;用于电力系统、生产厂家、科研单位等。 KDZD608A屏蔽服效率测试装置根据国家标准GB/T 6568-2008《带电作业用屏蔽服装》要求而设计&#xff0c;再结合电力行业标准DLT 976-201…

二、RocketMQ消息存储源码分析

RocketMQ源码深入剖析 6 Broker源码分析 Broker模块涉及到的内容非常多&#xff0c;本课程重点讲解以下技术点&#xff1a; 1、Broker启动流程分析 2、消息存储设计 3、消息写入流程 4、亮点分析&#xff1a;NRS与NRC的功能号设计 5、亮点分析&#xff1a;同步双写数倍性…

视频孪生赋能智慧水利数智化管理

在《关于大力推进智慧水利建设的指导意见》、《“十四五”智慧水利建设规划》中&#xff0c;水利部明确智慧水利建设目标&#xff1a;到2025年建成七大江河数字孪生流域&#xff0c;即&#xff1a; 到2025年&#xff0c;通过建设数字孪生流域、“2N”水利智能业务应用体系、水…

编程会不会被中英文转换干扰?

目录 引言&#xff1a; 解决办法&#xff1a; 图片&#xff1a; 引言&#xff1a; 我在编程的时候总是容易误触&#xff53;&#xff48;&#xff49;&#xff46;&#xff54;&#xff0c;导致代码编译错误&#xff0c;我都不知道哪里出了问题&#xff0c;每次基本要重…

2023志愿填报区块链专业

随着区块链技术的迅速发展&#xff0c;区块链专业毕业生在就业市场上拥有广阔的前景。他们可以在多个领域找到就业机会&#xff0c;从而实现职业发展和稳定的薪资待遇。 首先&#xff0c;金融行业是区块链专业毕业生的主要就业领域之一。银行、证券公司和支付机构等金融机构对…

【Lesson 02】 TiDB Server

目录 0 章节目标 一 TiDB Server架构 二 TiDB Server 作用​ 三 TiDB Server 的进程 1 SQL语句的解析和编译 2 SQL读写相关模块 3 在线DDL相关模块 4 GC机制与相关模块 四 TiDB Server的缓存 1 TIDB的缓存组成 2 TiDB 缓存管理 3 热点小表缓存 4 热点小表缓存-原理 0…

node基于express+mongodb项目的整体结构搭建和逻辑抽离

一、为什么需要逻辑抽离 这是我用express实现的一个缩减版的注册功能,如下&#xff1a; app.js const express require("express"); const app express();// 连接数据库 const mongoose require("mongoose"); // 连接数据库myTest mongoose.connect(…

python接口自动化(三十六)-封装与调用--流程类接口关联续集(详解)

简介 上一篇已经给大家都介绍过了流程类接口关联&#xff0c;但是由于博客的登录机制改变&#xff0c;所以没有办法给小伙伴们实战演练一下&#xff0c;那么这篇就按照上一篇计划的用jenkins来给小伙伴们演示一下流程类接口的封装和调用&#xff0c;其实很简单&#xff0c;就是…

RestTemplate和Sentinel整合

引入restTemplate EnableDiscoveryClient EnableFeignClients SpringBootApplication public class OrderApplication {BeanLoadBalanced//添加注解SentinelRestTemplateSentinelRestTemplatepublic RestTemplate getRestTemplate() {return new RestTemplate();}public stati…

【已解决】html元素如何使字体占据相同的元素显得整齐

本博文源于自身的亲身实践&#xff0c;让html的文本元素对齐&#xff0c;如果不让其对齐就会变得很丑陋&#xff0c;如下图&#xff0c;那么如何设置才能让元素占据相同呢&#xff1f; 文章目录 1、问题来源2、问题解决思路3、问题解决方案4、问题完整源码及效果 1、问题来源 …

animation.css无法显示动画效果问题解决

在使用【微信开发者工具】开发微信小程序时发现无法在开发者工具中展示出动画效果来 但是真机调试中可以正常的显示动画效果 【关于微信小程序中如何使用animation.css&#xff0c;参考微信小程序使用animation.css_THE WHY的博客-CSDN博客 】 同时发现在官网上点击各个动画并…

Django_POST请求的CSRF验证

目录 正常验证CSRF form表单 ajax的POST请求 关闭CSRF验证 源码等资料获取方法 django的POST接口发起请求默认清空下需要进行CSRF验证 正常验证CSRF form表单 如果form表单直接在标签之间添加{{ csrf_token }}就可以完成验证 ajax的POST请求 ajax的post需要在请求的he…

【Vue2.0源码学习】模板编译篇-模板解析阶段(整体运行流程)

文章目录 1. 前言2. 什么是模板编译3. 整体渲染流程4. 模板编译内部流程4.1 抽象语法树AST4.2 具体流程 5. 小总结6. 整体流程7. 回到源码8. 总结 1. 前言 在前几篇文章中&#xff0c;我们介绍了Vue中的虚拟DOM以及虚拟DOM的patch(DOM-Diff)过程&#xff0c;而虚拟DOM存在的必…

阿里云AliYun物联网平台使用-设备添加以及模拟设备端上云

一、前言 上一篇文章提到&#xff0c;我们已经申请了免费的阿里云平台&#xff0c;下面需要将我们的设备在阿里云上进行注册和申请&#xff0c;以便于我们的数据上云。 二、步骤 注册产品&#xff08;设备模型&#xff09; 在产品页面&#xff0c;点击 "创建产品" 。…

DevExpress WinForms TreeList控件,让业务数据展示更清晰!(一)

DevExpress WinForms的TreeList控件是一个功能齐全、数据感知的TreeView-ListView的混合体&#xff0c;它可以以树形、网格或两者结合的形式显示数据信息。无论是数据绑定模式还是非绑定模式&#xff0c;都具有完整的数据编辑支持。 PS&#xff1a;DevExpress WinForm拥有180组…

【测试开发】Junit 框架

目录 一. 认识 Junit 二. Junit 的常用注解 1. Test 2. Disabled 3. BeforeAll 4. AfterAll 5. BeforeEach 6. AfterEach 7. 执行测试 三. 参数化 1. 引入依赖 2. 单参数 3. 多参数 3.1 通过CSV实现 3.2 通过方法实现 4. 测试用例的执行顺序 四. 断言 五…

设计模式【结构型】-- 装饰者模式

装饰模式&#xff08;Decorator Pattern&#xff09; 定义 装饰者模式是一种结构型设计模式&#xff0c;它允许你动态地将新功能添加到对象中&#xff0c;通过将对象放入包含这些功能的特殊包装器对象中。这样一来&#xff0c;你可以在运行时通过组合不同的对象来扩展功能&…

用手机号注册亚马逊买家号需要注意什么问题

亚马逊平台的买家号可以用手机号注册也可以用邮箱进行注册&#xff0c;注册方法都是打开官网后点击注册&#xff0c;根据提示输入账号信息。当使用手机号注册亚马逊买家号时&#xff0c;有一些问题需要注意&#xff1a; 1、确保手机号的准确性&#xff1a;正确的手机号码非常重…

Android 系统的分区介绍

由于Android系统采用Linux架构&#xff0c;所以Android的系统分区可以类比同样采用Linux架构的操作系统&#xff08;如Windows&#xff09;。 Android系统分区分类 现在一般常见的Android分区方式共有三种&#xff0c;在不同的Android系统版本上会采用不同的分区方式。 1、传…