PyQt6实例_pyqtgraph多曲线显示工具_代码分享

news2025/4/24 22:25:42

目录

概述

效果

代码 

返回结果对象

字符型横坐标

通用折线图工具

工具主界面

使用举例


概述

1 分析数据遇到需要一个股票多个指标对比或一个指标多个股票对比,涉及到同轴多条曲线的显示,所以开发了本工具。

2 多曲线显示部分可以当通用工具使用。

3 数据计算使用compile动态执行代码,返回固定格式的数据进行显示,尽最大可能实现工具的灵活性。

效果

代码 

返回结果对象

import pandas as pd
from dataclasses import dataclass,field
@dataclass
class MultiLineObj:
    title:str=''
    df:pd.DataFrame=pd.DataFrame()
    col_dict: dict = field(default_factory=dict)
    error_msg:str = ''
    status:str='ok'
    pass

1 对应结果中的 title

2 字段中文名取自 col_dict = {df中的列名:中文名}

3 对应df中的数据,还用于右上角下载按钮,点击下载按钮可以将df数据下载到本地 

字符型横坐标

class StrAxisItem(pg.AxisItem):
    def __init__(self,ticks,*args,**kwargs):
        pg.AxisItem.__init__(self,*args,**kwargs)
        self.x_values = [x[0] for x in ticks]
        self.x_strings = [x[1] for x in ticks]
        pass
    def tickStrings(self, values, scale, spacing):
        strings = []
        for v in values:
            vs = v*scale
            if vs in self.x_values:
                vstr = self.x_strings[self.x_values.index(vs)]
            else:
                vstr = ''
            strings.append(vstr)
        return strings

继承pg.AxisItem, 重写tickStrings方法

通用折线图工具

class MultiLineGraphWidget(pg.PlotWidget):
    def __init__(self):
        super().__init__()
        self.init_data()
        pass
    def init_data(self):
        self.whole_df:pd.DataFrame = pd.DataFrame()
        # pd的col名:显示名
        self.whole_col_dict:dict = {}
        self.color_10_list = [(30,144,255),(138,43,226),(220,20,60),(0,128,128),(0,255,255),(0,250,154),(173,255,47),(255,255,224),(255,215,0),(255,140,0)]
        pass
    def set_data(self,df:pd.DataFrame,col_dict:dict):
        self.clear()
        self.addLegend()

        self.whole_df = df
        self.whole_col_dict = col_dict

        x = df['x'].to_list()
        xTicks = df.loc[:, ['x', 'reportDate']].values

        i = 0
        for k,v in col_dict.items():
            one_color = self.color_10_list[i%len(self.color_10_list)]
            one_curve = pg.PlotCurveItem(x=np.array(x),y=np.array(df[k].to_list()),pen=pg.mkPen({'color':one_color,'width':2}),connect='finite',name=v)
            self.addItem(one_curve)
            i += 1
            pass

        horAxis = StrAxisItem(ticks=xTicks, orientation='bottom')
        self.setAxisItems({'bottom':horAxis})

        self.vLine = pg.InfiniteLine(angle=90,movable=False)
        self.hLine = pg.InfiniteLine(angle=0,movable=False)
        self.label = pg.TextItem()

        self.addItem(self.vLine,ignoreBounds=True)
        self.addItem(self.hLine,ignoreBounds=True)
        self.addItem(self.label,ignoreBounds=True)

        self.vb = self.getViewBox()
        self.proxy = pg.SignalProxy(self.scene().sigMouseMoved, rateLimit=60, slot=self.pw_mouseMoved)
        self.enableAutoRange()
        pass
    def pw_mouseMoved(self, evt):
        pos = evt[0]
        if self.sceneBoundingRect().contains(pos):
            mousePoint = self.vb.mapSceneToView(pos)
            index = int(mousePoint.x())
            if index>=0 and index<len(self.whole_df):
                html_str = '<p style="color:white;font-size:18px;">'
                html_str += f"<br/>日期:{self.whole_df.loc[self.whole_df['x'] == index].iloc[0]['reportDate']}"
                for k,v in self.whole_col_dict.items():
                    html_str += f"<br/>{v}:{self.whole_df.loc[self.whole_df['x'] == index].iloc[0][k]:,}"
                    pass
                html_str += '</p>'
                self.label.setHtml(html_str)
                self.label.setPos(mousePoint.x(),mousePoint.y())
                pass
            self.vLine.setPos(mousePoint.x())
            self.hLine.setPos(mousePoint.y())
        pass
    def wheelEvent(self,ev):
        if len(self.whole_df) <= 0:
            super().wheelEvent(ev)
        else:
            delta = ev.angleDelta().x()
            if delta == 0:
                delta = ev.angleDelta().y()

            s = 1.001 ** delta

            before_xmin, before_xmax = self.viewRange()[0]
            val_x = self.getViewBox().mapSceneToView(ev.position()).x()

            after_xmin = int(val_x - (val_x - before_xmin) // s)
            after_xmax = int(val_x + (before_xmax - val_x) // s)

            if after_xmin < 1:
                after_xmin = 0
            if after_xmin >= len(self.whole_df):
                after_xmin = max(len(self.whole_df) - 3, len(self.whole_df) - 1)
            if after_xmax < 1:
                after_xmax = min(len(self.whole_df) - 1, 1)
            if after_xmax >= len(self.whole_df):
                after_xmax = len(self.whole_df) - 1

            df00 = self.whole_df.loc[(self.whole_df['x'] >= after_xmin) & (self.whole_df['x'] <= after_xmax)].copy()
            min_list = []
            max_list = []
            for k in self.whole_col_dict.keys():
                min_list.append(df00[k].min())
                max_list.append(df00[k].max())
                pass
            after_ymin = min(min_list)
            after_ymax = max(max_list)

            self.setXRange(after_xmin, after_xmax)
            self.setYRange(after_ymin, after_ymax)
            pass
    pass

1)工具中设置了10种颜色,不同曲线将显示不同颜色,如果曲线个数超过10个,将循环使用颜色

2)set_data方法需要带入df 和 col_dict两个参数

2.1)df 必须要有 x 、reportDate 两个字段,x为递增整数,reportDate为横坐标要显示的字符,reportDate为字符型。

2.2)折线的y轴数据在df中的列名为 col_dict中的key值,建议列名为英文和数字组成,col_dict中的val为中文名

工具主界面

class PyExcuteGraphShowWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('py文件执行并显示结果')
        self.setMinimumSize(QSize(1000,800))

        label00 = QLabel('选择py文件:')
        self.lineedit_file = QLineEdit()
        btn_choice = QPushButton('选择文件',clicked=self.btn_choice_clicked)
        self.btn_excute = QPushButton('执行',clicked=self.btn_excute_clicked)
        btn_download = QPushButton('下载数据',clicked=self.btn_download_clicked)

        self.label_title = QLabel('指标', alignment=Qt.AlignmentFlag.AlignHCenter)
        self.label_title.setStyleSheet("font-size:28px;color:#CC2EFA;")
        self.pw = MultiLineGraphWidget()

        layout00 = QHBoxLayout()
        layout00.addWidget(label00)
        layout00.addWidget(self.lineedit_file)
        layout00.addWidget(btn_choice)
        layout00.addWidget(self.btn_excute)
        layout00.addWidget(btn_download)

        layout = QVBoxLayout()
        layout.addLayout(layout00)
        layout.addWidget(self.label_title)
        layout.addWidget(self.pw)
        self.setLayout(layout)
        pass
    def open_init(self):
        self.whole_resObj:MultiLineObj = None
        pass
    def btn_choice_clicked(self):
        file_path,_ = QFileDialog.getOpenFileName(self,'选择文件')
        if file_path:
            self.lineedit_file.setText(file_path)
        pass
    def btn_excute_clicked(self):
        file_path = self.lineedit_file.text()
        if len(file_path) <= 0:
            QMessageBox.information(self,'提示','请选择要执行的py文件',QMessageBox.StandardButton.Ok)
            return
        with open(file_path,'r',encoding='utf-8') as fr:
            py_code = fr.read()
        namespace = {}
        fun_code = compile(py_code, '<string>', 'exec')
        exec(fun_code, namespace)
        res = namespace['execute_caculate']()
        if res.status == 'error':
            QMessageBox.information(self,'执行过程报错',res.error_msg,QMessageBox.StandardButton.Ok)
            return
        self.label_title.setText(res.title)
        self.whole_resObj = res
        df = res.df.copy()
        df['x'] = range(len(df))
        self.pw.set_data(df.copy(),res.col_dict)
        QMessageBox.information(self,'提示','执行完毕',QMessageBox.StandardButton.Ok)
        pass

    def btn_download_clicked(self):
        if self.whole_resObj is None or self.whole_resObj.status == 'error':
            QMessageBox.information(self,'提示','数据为空',QMessageBox.StandardButton.Ok)
            return
        dir_name = QFileDialog.getExistingDirectory(self,'选择保存位置')
        if dir_name:
            df = self.whole_resObj.df.copy()
            df.rename(columns=self.whole_resObj.col_dict,inplace=True)
            df.to_csv(dir_name+os.path.sep + self.whole_resObj.title +'.csv',encoding='utf-8',index=False)
            QMessageBox.information(self,'提示','下载完毕',QMessageBox.StandardButton.Ok)
            pass
    pass

使用举例

需要导入的包和运行代码

import os,sys
import pandas as pd
import numpy as np
from PyQt6.QtCore import (
QSize,
Qt
)
from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QPushButton,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QFileDialog,
    QMessageBox,
    QLineEdit
)
import pyqtgraph as pg
from objects import MultiLineObj

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mw = PyExcuteGraphShowWidget()
    mw.show()
    app.exec()
    pass

1)一个py文件例子,内容如下,方法名固定为 execute_caculate

def execute_caculate():
    import traceback
    import pandas as pd
    from utils import postgresql_utils
    from objects import MultiLineObj
    '''
    灵活py文件执行
    营业利润,营业外支出,营业外收入
    '''
    conn = postgresql_utils.connect_db()
    cur = conn.cursor()
    try:
        ticker = '000638'
        sql_str = f'''
        select reportDate,iii_operateProfit,add_nonoperateIncome,less_nonoperateExpenses from t_profit where ticker=\'{ticker}\' and reportDate like \'%-12-31\';
        '''
        cur.execute(sql_str)
        res = cur.fetchall()
        col_list = ['reportDate','a0','a1','a2']
        col_dict = {
            'a0':'营业利润',
            'a1':'营业外收入',
            'a2':'营业外支出'
        }
        df = pd.DataFrame(columns=col_list, data=res)
        res_obj = MultiLineObj(
            title=f'{ticker},营业利润、营业外收入、营业外支出',
            df=df,
            col_dict=col_dict,
            status='ok'
        )
        return res_obj
    except:
        res_obj = MultiLineObj(
            status='error',
            error_msg=traceback.format_exc()
        )
        return res_obj
    finally:
        cur.close()
        conn.close()
        pass
    pass



保存为 test002.py

注意:例子中涉及到的postgreSQL和财报数据在往期博文中可以找到。

2)点击“选择文件”,选择 test002.py文件

3)点击“执行”,执行完毕后就能显示效果图

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

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

相关文章

Linux网络编程 多线程Web服务器:HTTP协议与TCP并发实战

问题解答 TCP是如何防止SYN洪流攻击的&#xff1f; 方式有很多种&#xff0c;我仅举例部分&#xff1a; 1、调整内核参数 我们知道SYN洪流攻击的原理就是发送一系列无法完成三次握手的特殊信号&#xff0c;导致正常的能够完成三次握手的信号因为 连接队列空间不足&#xff…

Qt 下载的地址集合

Qt 下载离线安装包 download.qt.io/archive/qt/5.14/5.14.2/ Qt 6 安装下载在线安装包 Index of /qt/official_releases/online_installers/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

ubuntu下gcc/g++安装及不同版本切换

1. 查看当前gcc版本 $ gcc --version# 查看当前系统中已安装版本 $ ls /usr/bin/gcc*2. 安装新版本gcc $ sudo apt-get update# 这里以版本12为依据&#xff08;也可以通过源码方式安装&#xff0c;请自行Google&#xff01;&#xff09; $ sudo apt-get install -y gcc-12 g…

FPGA入门学习Day1——设计一个DDS信号发生器

目录 一、DDS简介 &#xff08;一&#xff09;基本原理 &#xff08;二&#xff09;主要优势 &#xff08;三&#xff09;与传统技术的对比 二、FPGA存储器 &#xff08;一&#xff09;ROM波形存储器 &#xff08;二&#xff09;RAM随机存取存储器 &#xff08;三&…

微信小程序拖拽排序有效果图

效果图 .wxml <view class"container" style"--w:{{w}}px;" wx:if"{{location.length}}"><view class"container-item" wx:for"{{list}}" wx:key"index" data-index"{{index}}"style"--…

WT2000T专业录音芯片:破解普通录音设备信息留存、合规安全与远程协作三大难题

在快节奏的现代商业环境中&#xff0c;会议是企业决策、创意碰撞和战略部署的核心场景。然而&#xff0c;传统会议记录方式常面临效率低、信息遗漏、回溯困难等痛点。如何确保会议内容被精准记录并高效利用&#xff1f;会议室专用录音芯片应运而生&#xff0c;以智能化、高保真…

【Python 学习笔记】 pip指令使用

系列文章目录 pip指令使用 文章目录 系列文章目录前言安装配置使用pip 管理Python包修改pip下载源 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 当前文章记录的是我在学习过程的一些笔记和思考&#xff0c;可能存在有误解的地方&#xff0c;仅供大家…

C# 文件读取

文件读取是指使用 C# 程序从计算机文件系统中获取文件内容的过程。将存储在磁盘上的文件内容加载到内存中&#xff0c;供程序处理。主要类型有&#xff1a;文本文件读取&#xff08;如 .txt, .csv, .json, .xml&#xff09;&#xff1b;二进制文件读取&#xff08;如 .jpg, .pn…

leetcode125.验证回文串

class Solution {public boolean isPalindrome(String s) {s s.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();for(int i0,js.length()-1;i<j;i,j--){if(s.charAt(i)!s.charAt(j))return false;}return true;} }

【Android面试八股文】Android系统架构【一】

Android系统架构图 1.1 安卓系统启动 1.设备加电后执行第一段代码&#xff1a;Bootloader 系统引导分三种模式&#xff1a;fastboot&#xff0c;recovery&#xff0c;normal&#xff1a; fastboot模式&#xff1a;用于工厂模式的刷机。在关机状态下&#xff0c;按返回开机 键进…

【数据可视化-21】水质安全数据可视化:探索化学物质与水质安全的关联

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

【prometheus+Grafana篇】从零开始:Linux 7.6 上二进制安装 Prometheus、Grafana 和 Node Exporter

&#x1f4ab;《博主主页》&#xff1a;奈斯DB-CSDN博客 &#x1f525;《擅长领域》&#xff1a;擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对SQLserver、NoSQL(MongoDB)有了解 &#x1f496;如果觉得文章对你有所帮…

STM32(M4)入门:GPIO与位带操作(价值 3w + 的嵌入式开发指南)

一&#xff1a;GPIO 1.1 了解时钟树&#xff08;必懂的硬件基础&#xff09; 在 STM32 开发中&#xff0c;时钟系统是一切外设工作的 “心脏”。理解时钟树的工作原理&#xff0c;是正确配置 GPIO、UART 等外设的核心前提。 1.1.1 为什么必须开启外设时钟&#xff1f; 1. 计…

Linux419 三次握手四次挥手抓包 wireshark

还是Notfound 没连接 可能我在/home 准备配置静态IP vim ctrlr 撤销 u撤销 配置成功 准备关闭防火墙 准备配置 YUM源 df -h 未看到sr0文件 准备排查 准备挂载 还是没连接 计划重启 有了 不重启了 挂载准备 修改配置文件准备 准备清理缓存 ok 重新修改配…

CSS-跟随图片变化的背景色

CSS-跟随图片变化的背景色 获取图片的主要颜色并用于背景渐变需要安装依赖 colorthief获取图片的主要颜色. 并丢给背景注意 getPalette并不是个异步方法 import styles from ./styles.less; import React, { useState } from react; import Colortheif from colorthief;cons…

解决Docker 配置 daemon.json文件后无法生效

vim /etc/docker/daemon.json 在daemon中配置一下dns {"registry-mirrors": ["https://docker.m.daocloud.io","https://hub-mirror.c.163.com","https://dockerproxy.com","https://docker.mirrors.ustc.edu.cn","ht…

虚幻基础:ue碰撞

文章目录 碰撞&#xff1a;碰撞体 运动后 产生碰撞的行为——碰撞响应由引擎负责&#xff0c;并向各自发送事件忽略重叠阻挡 碰撞响应关系有忽略必是忽略有重叠必是重叠有阻挡不一定阻挡&#xff08;双方都为阻挡&#xff09; 碰撞启用&#xff1a;纯查询&#xff1a;开启移动检…

数据治理体系的“三驾马车”:质量、安全与价值挖掘

1. 执行摘要 数据治理已从合规驱动的后台职能&#xff0c;演变为驱动业务成果的战略核心。本文将深入探讨现代数据治理体系的三大核心驱动力——数据质量、数据安全与价值挖掘——它们共同构成了企业在数字时代取得成功的基石。数据质量是信任的基石&#xff0c;确保决策所依据…

leetcode 二分查找应用

34. Find First and Last Position of Element in Sorted Array 代码&#xff1a; class Solution { public:vector<int> searchRange(vector<int>& nums, int target) {int low lowwer_bound(nums,target);int high upper_bound(nums,target);if(low high…

Ngrok 内网穿透实现Django+Vue部署

目录 Ngrok 配置 注册/登录 Ngrok账号 官网ngrok | API Gateway, Kubernetes Networking Secure Tunnels 直接cmd运行 使用随机生成网址&#xff1a;ngrok http 端口号 使用固定域名生成网址&#xff1a;ngrok http --domain你的固定域名 端口号 Django 配置 1.Youre a…