Qt自定义Plot实现曲线绘制的详细过程


Posted in Python onNovember 02, 2021

简介

实现了qt绘制曲线功能,包含arm触摸屏多点触控缩放(只支持两点),实时曲线绘制,数据点根据绘制宽度优化,跟踪点数据获取,双坐标等功能

演示

Qt自定义Plot实现曲线绘制的详细过程

Qt自定义Plot实现曲线绘制的详细过程

代码

头文件 plot.h

/*
 * 作者:老人与海
 * 博客:https://blog.csdn.net/qq_41340733
 * 代码不保证稳定性,请勿用于商业用途
 */

#ifndef PLOT_H
#define PLOT_H

#include <QWidget>
#include <QTimer>
#include <QList>
#include <QWheelEvent>
#include <QMouseEvent>
#include "plotdata.h"
typedef struct _RangeVal
{
    double Beg;
    double End;
    double offSet;
    bool IsOffSet;
    _RangeVal()
    {
        Beg=0;
        End=0;
        offSet=0;
        IsOffSet=false;
    }
}_RangeVal;

//鼠标跟中点
class Tracking
{
public:
    Tracking()
    {
        m_IsDraw=false;
        m_IsMove=false;
        m_IsInit=false;
        m_First=false;
        m_radius=25;
        m_PointCenter=QPoint(0,0);
    }
    ~Tracking(){}
    bool getIsRange(QPoint point)
    {
        if(point.x()>(m_PointCenter.x()-m_radius)&&
                point.x()<(m_PointCenter.x()+m_radius)&&
                point.y()>(m_PointCenter.y()-m_radius)&&
                point.y()<(m_PointCenter.y()+m_radius))
        {
            return true;
        }else{
            return false;
        }
    }
public:
    bool m_IsDraw;
    bool m_IsMove;//鼠标焦点
    bool m_IsInit;
    bool m_First;
    int m_radius;
    QPoint m_PointCenter;//绘制圆心
};

/**
 * @brief The CoorAxis class
 * 类名:CoorAxis
 * 功能:坐标轴部件,用来绘制XY坐标轴,使用时必须用Plot的ticker类进行初始化,否则无法正常绘制坐标
 */
class CoorAxis : public QWidget
{
    Q_OBJECT
public:
    explicit CoorAxis(QWidget *parent = nullptr,Ticker *ticker= nullptr);
    virtual ~CoorAxis();

    Ticker *ticker(){ return m_Ticker; }
    void mouseMove(QMouseEvent *event);
signals:
    void sig_UpdateUI();
public slots:
    void slots_sendCoorWheel(QWheelEvent *event);
    void slots_sendCoorMouse(QMouseEvent *event,QPoint point,bool IsFirst=false);
protected:
   void paintEvent(QPaintEvent *event);
   void drawTickerX(QPainter *painter);//绘制X轴
   void drawTickerY_L(QPainter *painter);//绘制左边Y轴
   void drawTickerY_R(QPainter *painter);//绘制右边Y轴
   void wheelEvent(QWheelEvent *event);//滚轮事件,缩放曲线图像
   void mousePressEvent(QMouseEvent *event);//按下事件,记录坐标和按下标志
   void mouseReleaseEvent(QMouseEvent *event);//抬起事件,取消按下标志
   void mouseMoveEvent(QMouseEvent *event);//鼠标移动事件,移动曲线

protected:
   Ticker *m_Ticker;
   int m_LastLength;
   int m_margin;

   bool IsPress=false;
   QPoint m_FirstPoint;
};


class Plot : public QWidget
{
    Q_OBJECT
public:
    explicit Plot(QWidget *parent=nullptr);
    virtual ~Plot();

    void ValueInit();
    Ticker *tickerX();
    Ticker *tickerL();
    Ticker *tickerR();
    void addGraph();
    void GraphClear(){ m_XyGraph.clear(); }
    int getGraphCount(){ return m_XyGraph.count(); }
    _XYList *Graph(int index);
    QList<_XYList> &GraphGroup(){ return m_XyGraph; }
    void setTrackVisibel(bool isVisbel){ m_Tracking.m_IsDraw=isVisbel; }
    bool getTrackVisibel(){ return m_Tracking.m_IsDraw; }
    void setIsDrawPath(bool value){ m_IsDrawPath=value; }
    bool getIsDrawPath(){ return m_IsDrawPath; }

    void coordToPixel(_COORDINATEVALUE *Value,_TICKERTYPE Type);
    void coordToPixel(double &key,double &value,_TICKERTYPE Type);
    void PixelToCoorKey(double &key,_TICKERTYPE Type);
    void PixelToCoorValue(double &value,_TICKERTYPE Type);
    void PixelToCoord(QPointF &pointf,_TICKERTYPE Type);
    void getOptimizedLineData(QVector<QPointF> *lineData,int index);

    void calculateTrackValue(QPoint Point);
    void setBackgroundColor(QColor color){ m_BackgroundColor=color; }
    QColor getBackgroundColor(){ return m_BackgroundColor; }
signals:
    void sig_sendCoorWheel(QWheelEvent *event);
    void sig_sendCoorMouse(QMouseEvent *event,QPoint point,bool IsFirst=false);
    void sig_UpdateUI();
    void sig_UpdateTrack();//刷新跟踪点数据
public slots:

protected:
   void paintEvent(QPaintEvent *event);
   void drawGrid(QPainter *painter);//绘制网格
   void drawPath(QPainter *painter);//绘制曲线
   void drawCursor(QPainter *painter);//绘制跟踪点
   void drawTickerX(QPainter *painter);//绘制X轴
   void drawTickerY_L(QPainter *painter);//绘制左边Y轴
   void drawTickerY_R(QPainter *painter);//绘制右边Y轴

   void wheelEvent(QWheelEvent *event);//滚轮事件,缩放曲线图像
   void mousePressEvent(QMouseEvent *event);//按下事件,记录坐标和按下标志
   void mouseReleaseEvent(QMouseEvent *event);//抬起事件,取消按下标志
   void mouseMoveEvent(QMouseEvent *event);//鼠标移动事件,移动曲线
   void showEvent(QShowEvent *event);//显示事件
   bool event(QEvent *event);//事件,实现多点触控功能,目前只对两个触控点做了处理

public:
   QPixmap m_PixMapCursor;//跟踪点绘制图像缓冲
   Tracking m_Tracking;
protected:
   Ticker *m_TickerX;
   Ticker *m_TickerL;
   Ticker *m_TickerR;

   bool IsPress=false;
   bool m_IsTouch=false;

   QPoint m_ClickedPoint;
   qreal m_scaleFactorX=0.0;
   qreal m_scaleFactorY=0.0;
   QList<_XYList> m_XyGraph;

   QColor m_BackgroundColor;
   bool m_IsDrawPath;//是否绘制曲线,在多线程添加数据的时候调用,添加数据时禁止绘制曲线
private:
   friend class Ticker;
};

class CurveWid : public QWidget
{
    Q_OBJECT
public:
    explicit CurveWid(QWidget *parent = nullptr);

    void DataInit();
    void reUpdate();
    void reUpdateAll();
signals:

public slots:
    void slots_UpdateUI();
    void slots_TimeOut();
protected:
    void paintEvent(QPaintEvent *event);
public:
    CoorAxis *m_AxisX;
    CoorAxis *m_AxisL;
    CoorAxis *m_AxisR;

    Plot *m_Plot;
    QTimer *m_Timer;
};


#endif // PLOT_H

头文件plotdata.h

/*
 * 作者:老人与海 
 * 博客:https://blog.csdn.net/qq_41340733
 * 代码不保证稳定性,请勿用于商业用途
 */


#ifndef PLOTDATA_H
#define PLOTDATA_H

#include <QObject>
#include <QVector>
#include <QColor>
class CoorAxis;

//_COORDINATEVALUE单个数据点结构体
typedef struct _COORDINATEVALUE
{
    double Key;
    double Value;
    _COORDINATEVALUE()
    {
        Key=0;
        Value=0;
    }
}_COORDINATEVALUE;
//坐标轴绘制的文本类型,包括时间毫秒,时间秒,数值
typedef enum _TEXT_TYPE
{
    _TimeMs,
    _TimeS,
    CountVal
}_TEXT_TYPE;
//坐标轴类型,XAxis表示X轴,YAxis_L表示左边的Y轴,YAxis_R表示右边的Y轴
typedef enum _TICKERTYPE
{
    XAxis=0,
    YAxis_L=1,
    YAxis_R=2
}_TICKERTYPE;
//坐标缩放前的坐标值,_begVal坐标起始值,_endVal坐标结束值,_siteVal坐标中点,
//多点触控的时候先记录坐标的原始值,然后根据手势距离对原始进行缩放
typedef struct _Scale
{
    double _begVal=0.0;
    double _endVal=0.0;
    double _siteVal=0.0;
}_Scale;

/**
 * @brief The _XYList class
 * 功能:单条曲线的数据结构类型,包括坐标轴类型,曲线颜色,
 * 最多可绘制的曲线点数,等功能等;
 */
class _XYList
{
public:
    explicit _XYList();
    virtual ~_XYList(){}

    int Count();//实际有效的数据点
    int CountAll();//所有的数据点,包括被移除的记录
    int getRemoveCount(){ return  m_removeCount; }
    _COORDINATEVALUE &getPoint(int index);
    void addData(const double Key,const double Value);
    void removefirst();
    void cleanBuff();
    int size() const { return m_CoodValue.size()-m_removeCount; }
    QVector<_COORDINATEVALUE> *data();

    void setPointCountMax(int Count);
    int getPointMax(){ return m_MaxPointCount; }
    void setColor(QColor color){ m_Color=color; }
    QColor getColor(){ return m_Color; }
    void setAxisType(_TICKERTYPE type){ m_Type=type; }
    _TICKERTYPE &getAixsType(){ return m_Type; }

    int getBegindex(const double &TmpKey);
    int getEndindex(const double &TmpKey);
    int getCoorToIndex(const double &TmpKey);

    _COORDINATEVALUE getTrackValue(){ return TrackValue; }
    void setTrackValue(const _COORDINATEVALUE value){ TrackValue=value; }
    void setTrackValue(const double key,const double value){
        TrackValue.Key=key;
        TrackValue.Value=value;
    }
    bool TracckIsValid(){ return IsValid; }
    void setTrackIsvalid(bool val){ IsValid=val; }

    void setVisible(bool val){ m_Visible=val; }
    bool getVisible(){ return m_Visible; }

    _COORDINATEVALUE getMaxValue(){ return m_MaxValue; }
    _COORDINATEVALUE getMinValue(){ return m_MinValue; }

    QVector<_COORDINATEVALUE>::const_iterator constBegIte(){ return (m_CoodValue.begin()+m_removeCount); }
    QVector<_COORDINATEVALUE>::const_iterator constEndIte(){ return m_CoodValue.end(); }
protected:
    QVector<_COORDINATEVALUE> m_CoodValue;
    int m_MaxPointCount;//实际数据点,根据总数据点是否达到这和数目而对m_removeCount进行操作
    int m_removeCount;//被移除的次数,会先记录,到达一定数据时候会重新分分配m_CoodValue大小,提高了添加固定点数据时候的速度
    _TICKERTYPE m_Type;
    QColor m_Color;
    _COORDINATEVALUE TrackValue;//踪点的值
    bool IsValid;//判断跟踪点的值是否有效
    bool m_Visible;//是否绘制,既可见度

    _COORDINATEVALUE m_MaxValue;
    _COORDINATEVALUE m_MinValue;
};
/**
 * @brief The Ticker class
 * 坐标轴数据结构类,这里描述了坐标的主刻度,子刻度,刻度长度,
 * 坐标范围,坐标轴的类型(X还是Y 参考_TICKERTYPE)等信息
 */
class Ticker
{
public:
    explicit Ticker()
    {
       MainLineCount=15;
       SubLineCount=6;
       Mainlength=10;
       Sublength=10;
       Type=_TICKERTYPE::XAxis;
       m_RangeBeg=0;
       m_RangeEnd=500;
    }
    virtual ~Ticker(){}

    void setMainLineCount(const int count){ MainLineCount=count; }
    void setSubLineCount(const int count){ SubLineCount=count; }
    void setMainLineLength(const int length){ Mainlength=length; }
    void setSubLineLength(const int length){ Sublength=length; }
    void setAxisType(_TICKERTYPE type){ Type=type; }
    void setTextType(_TEXT_TYPE Type){ m_TextType=Type; }
    void setRangeBeg(const double &Value);
    void setRangeEnd(const double &Value);
    void setRange(const double &BegValue,const double &EndValue);
    void moveRange(const double &Value);//根据数值偏移,正数往前,负数往后
    void moveRangePercent(const double &Value);//根据百分比偏移,正数往前,负数往后
    void setZoom(const double value);//根据中心值进行缩放,大于1表示放大,小于1表示缩小

    int getMainLineCount(){ return MainLineCount; }
    int getSubLineCount(){ return SubLineCount; }
    int getMainLineLength(){ return Mainlength; }
    int getSubLineLength(){ return Sublength; }
    _TICKERTYPE getAxisType(){ return Type; }
    double getRangeBeg(){ return m_RangeBeg; }
    double getRangeEnd(){ return m_RangeEnd; }
    double getRangeMidvalue(){ return (m_RangeBeg+(m_RangeEnd-m_RangeBeg)/2.0); }//获取轴的中心值
    _Scale &getScale(){ return m_Scale; }
    void InitScale();
    void setScaleCoor(const double scaleFactor);//根据siteVal左边点进行缩放
    _TEXT_TYPE getTextType(){ return m_TextType; }
protected:
    int MainLineCount;
    int SubLineCount;
    int Mainlength;
    int Sublength;
    double m_RangeBeg;
    double m_RangeEnd;

    _TICKERTYPE Type;
    _TEXT_TYPE m_TextType;
    _Scale m_Scale;
private:
   friend class CoorAxis;
};
#endif // ADDPOINT_H

头文件mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "qtimer.h"
#include "plot.h"
#include <QLabel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

public slots:
    void slots_TimeOut();
    void slots_Refresh();
    void slots_UpdateTrack();
protected:
    QTimer *m_Timer;
    QTimer *m_TimerRefresh;

    CurveWid *m_Curve;

    QVector<QLabel *> m_labList;
    bool IsAddOk=false;
private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

c文件

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QDateTime>
#include <QDebug>
#include "qgridlayout.h"
#include "math.h"
#include "qpainter.h"
#include <QVBoxLayout>
#include <QGridLayout>

#define _POINTCOUNT 50000
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->statusbar->hide();


    ui->centralwidget->setStyleSheet("QWidget{background-color: rgb(0, 0, 0);}");
    m_Curve=new CurveWid;
    connect(m_Curve->m_Plot,&Plot::sig_UpdateTrack,this,&MainWindow::slots_UpdateTrack);

    m_Timer=new QTimer(this);
    m_TimerRefresh=new QTimer(this);
    m_Timer->start(100);
    m_TimerRefresh->start(100);
    connect(m_Timer,&QTimer::timeout,this,&MainWindow::slots_TimeOut);
    connect(m_TimerRefresh,&QTimer::timeout,this,&MainWindow::slots_Refresh);

    m_Curve->m_AxisL->ticker()->setRange(-100,1200);
    m_Curve->m_AxisR->ticker()->setRange(-100,600);
    m_Curve->m_AxisX->ticker()->setRange(-100,_POINTCOUNT*0.01);
    qint64 Time=QDateTime::currentMSecsSinceEpoch();
    m_Curve->m_AxisX->ticker()->setTextType(_TEXT_TYPE::_TimeMs);
//    m_Curve->m_AxisX->ticker()->setRange(Time-600000,Time);

    QGridLayout *G_layout=new QGridLayout;
    G_layout->setVerticalSpacing(3);
    G_layout->setContentsMargins(0,0,0,0);
    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
    for(int i=0;i<16;i++)
    {
        QLabel *labValue=new QLabel;
        labValue->setText(QString("Qlabel %1").arg(i+1));
        labValue->setAlignment(Qt::AlignCenter);
        labValue->setStyleSheet("QLabel{color:rgb(0, 122, 255);}");
        labValue->setFixedHeight(36);
        G_layout->addWidget(labValue,i/8,i%8);

        QColor color=QColor(qrand() % 256, qrand() % 256, qrand() % 256);
        int r,g,b;
        color.getRgb(&r,&g,&b);
        labValue->setStyleSheet(QString("QLabel{ color:rgb(%0,%1,%2);}").arg(r).arg(g).arg(b));
        m_labList.append(labValue);

        m_Curve->m_Plot->addGraph();
        m_Curve->m_Plot->Graph(i)->setColor(color);
        m_Curve->m_Plot->Graph(i)->setPointCountMax(_POINTCOUNT);
        if((i%2)!=0){
            m_Curve->m_Plot->Graph(i)->setAxisType(_TICKERTYPE::YAxis_R);
        }else{
            m_Curve->m_Plot->Graph(i)->setAxisType(_TICKERTYPE::YAxis_L);
        }
    }

    QVBoxLayout *V_layout=new QVBoxLayout;
    V_layout->setContentsMargins(0,0,0,0);
    V_layout->addWidget(m_Curve);
    V_layout->addLayout(G_layout);

    ui->centralwidget->setLayout(V_layout);


    for(int i=0;i<_POINTCOUNT*0.01;i++){
        slots_TimeOut();
    }
    IsAddOk=true;
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::slots_TimeOut()
{
    double w = (3.14/100)*5;  //w为角速度 ,可以理解为波浪的密度,越大密度越大
    double A = 40;    //  A表示振幅,可以理解为水波的高度,越大高度越高
    static double x;
    x++;
    double waveY = (double)(A * sin(w * x ));// waveY随着x的值改变而改变,从而得到正弦曲线

    static double count=0;
    count+=1;
    qint64 Time=QDateTime::currentMSecsSinceEpoch();
    for(int i=0;i<m_Curve->m_Plot->getGraphCount();i++)
    {
        int num = qrand()%(200-100)+100;//产生300-500范围的随机岁
        float data=0;//除以10获取 30.0到50.0的随机数
        data+=80*i+num;
//        m_Curve->m_Plot->Graph(i)->addData(count,data);
        m_Curve->m_Plot->Graph(i)->addData(count,waveY+80*i);
    }

    if(IsAddOk)
    {
        if(count>m_Curve->m_AxisX->ticker()->getRangeEnd())
        {
            m_Curve->m_AxisX->ticker()->moveRange(count-m_Curve->m_AxisX->ticker()->getRangeEnd());
        }
    }
}

void MainWindow::slots_Refresh()
{
    m_Curve->reUpdateAll();
}

void MainWindow::slots_UpdateTrack()
{
    for(int i=0;i<m_Curve->m_Plot->getGraphCount();i++){
        m_labList[i]->setText(QString::number(m_Curve->m_Plot->Graph(i)->getTrackValue().Value,'f',2));
    }
}

源码下载

工程:TestCurve
链接: 源码下载

到此这篇关于Qt自定义Plot实现曲线绘制的文章就介绍到这了,更多相关Qt自定义曲线绘制内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python中使用copy模块实现列表(list)拷贝
Apr 14 Python
Python简明入门教程
Aug 04 Python
Python中强大的命令行库click入门教程
Dec 26 Python
python数据类型_元组、字典常用操作方法(介绍)
May 30 Python
如何使用 Pylint 来规范 Python 代码风格(来自IBM)
Apr 06 Python
python使用socket创建tcp服务器和客户端
Apr 12 Python
详解python数据结构和算法
Apr 18 Python
Python测试Kafka集群(pykafka)实例
Dec 23 Python
ansible-playbook实现自动部署KVM及安装python3的详细教程
May 11 Python
如何在Python对Excel进行读取
Jun 04 Python
记一次Django响应超慢的解决过程
Sep 17 Python
解决Python字典查找报Keyerror的问题
May 26 Python
Python 正则模块详情
Nov 02 #Python
Python 数据可视化之Bokeh详解
Nov 02 #Python
Python 数据可视化之Matplotlib详解
分位数回归模型quantile regeression应用详解及示例教程
Python常遇到的错误和异常
Nov 02 #Python
Python 数据可视化之Seaborn详解
关于python中模块和重载的问题
You might like
PHP版国家代码、缩写查询函数代码
2011/08/14 PHP
PHP中strtotime函数使用方法分享
2012/01/10 PHP
基于PHP一些十分严重的缺陷详解
2013/06/03 PHP
PHP中new static()与new self()的比较
2016/08/19 PHP
php flush无效,IIS7下php实时输出的方法
2016/08/25 PHP
PHP实现超简单的SSL加密解密、验证及签名的方法示例
2017/08/28 PHP
NiftyCube——轻松实现圆角边框
2007/02/20 Javascript
JSQL 基于客户端的成绩统计实现方法
2010/05/05 Javascript
用正则表达式替换图片地址img标签
2013/11/22 Javascript
写JQuery插件的基本知识
2013/11/25 Javascript
javascript日期格式化方法小结
2015/12/17 Javascript
JavaScript简单实现鼠标移动切换图片的方法
2016/02/23 Javascript
javascript解决小数的加减乘除精度丢失的方案
2016/05/31 Javascript
浅谈Sublime Text 3运行JavaScript控制台
2016/06/06 Javascript
js事件冒泡、事件捕获和阻止默认事件详解
2016/08/04 Javascript
详解Vue2 无限级分类(添加,删除,修改)
2017/03/07 Javascript
jQuery选择器之子元素过滤选择器
2017/09/28 jQuery
对node通过fs模块判断文件是否是文件夹的实例讲解
2019/06/10 Javascript
vue移动端使用canvas签名的实现
2020/01/15 Javascript
Python引用类型和值类型的区别与使用解析
2017/10/17 Python
一道python走迷宫算法题
2018/01/22 Python
Pyqt QImage 与 np array 转换方法
2019/06/27 Python
Django高级编程之自定义Field实现多语言
2019/07/02 Python
如何通过python检查文件是否被占用
2020/12/18 Python
pip/anaconda修改镜像源,加快python模块安装速度的操作
2021/03/04 Python
html Table 表头固定的实现
2019/01/22 HTML / CSS
NEW LOOK官网:英国时装零售巨头之一,快时尚品牌
2017/01/11 全球购物
加拿大女包品牌:Matt & Nat
2017/05/12 全球购物
瑜伽国际:Yoga International
2018/04/18 全球购物
C语言基础笔试题
2013/04/27 面试题
物业经理求职自我评价
2013/09/22 职场文书
自荐信要包含哪些内容
2013/11/06 职场文书
中国好声音广告词
2014/03/18 职场文书
班级学习计划书
2014/04/27 职场文书
2015年教师国培感言
2015/08/01 职场文书
教学反思怎么写
2016/02/24 职场文书