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模拟鼠标拖动操作的方法
Mar 11 Python
菜鸟使用python实现正则检测密码合法性
Jan 05 Python
Python数组定义方法
Apr 13 Python
Python enumerate索引迭代代码解析
Jan 19 Python
Python supervisor强大的进程管理工具的使用
Apr 24 Python
Django 接收Post请求数据,并保存到数据库的实现方法
Jul 12 Python
Python列表删除元素del、pop()和remove()的区别小结
Sep 11 Python
Python之Class&amp;Object用法详解
Dec 25 Python
一文详述 Python 中的 property 语法
Sep 01 Python
python函数超时自动退出的实操方法
Dec 28 Python
一篇文章弄懂Python关键字、标识符和变量
Jul 15 Python
Python+Selenium实现读取网易邮箱验证码
Mar 13 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/05/24 PHP
php随机生成数字字母组合的方法
2015/03/18 PHP
删除PHP数组中的重复元素的实现代码
2017/04/10 PHP
如何通过View::first使用Laravel Blade的动态模板详解
2017/09/21 PHP
PHP如何使用JWT做Api接口身份认证的实现
2020/02/03 PHP
javascript ie6兼容position:fixed实现思路
2013/04/01 Javascript
jquery批量控制form禁用的代码
2013/08/06 Javascript
JavaScript获取当前日期是星期几的方法
2015/04/06 Javascript
简单讲解jQuery中的子元素过滤选择器
2016/04/18 Javascript
javascript基础知识
2016/06/07 Javascript
老生常谈javascript的类型转换
2016/10/12 Javascript
jQuery 出现Cannot read property ‘msie’ of undefined错误的解决方法
2016/11/23 Javascript
jQuery Validate让普通按钮触发表单验证的方法
2016/12/15 Javascript
微信小程序 本地存储及登录页面处理实例详解
2017/01/11 Javascript
jQuery插件echarts实现的循环生成图效果示例【附demo源码下载】
2017/03/04 Javascript
JS中cookie的使用及缺点讲解
2017/05/13 Javascript
JS原生数据双向绑定实现代码
2017/08/14 Javascript
微信小程序内拖动图片实现移动、放大、旋转的方法
2018/09/04 Javascript
微信小程序wx.request的简单封装
2019/11/13 Javascript
python实现的阳历转阴历(农历)算法
2014/04/25 Python
使用Python脚本对Linux服务器进行监控的教程
2015/04/02 Python
深入解析Python中的集合类型操作符
2015/08/19 Python
浅谈numpy中linspace的用法 (等差数列创建函数)
2017/06/07 Python
python中使用psutil查看内存占用的情况
2018/06/11 Python
设置python3为默认python的方法
2018/10/31 Python
快速排序的四种python实现(推荐)
2019/04/03 Python
Django框架之DRF 基于mixins来封装的视图详解
2019/07/23 Python
Django-rest-framework中过滤器的定制实例
2020/04/01 Python
经济系大学生求职信
2013/10/01 职场文书
启动仪式策划方案
2014/06/14 职场文书
邻里守望志愿服务活动方案
2014/08/15 职场文书
出纳年终工作总结2014
2014/12/05 职场文书
放射科岗位职责
2015/02/14 职场文书
自主招生推荐信怎么写
2015/03/26 职场文书
Python利器openpyxl之操作excel表格
2021/04/17 Python
Mysql文件存储图文详解
2021/06/01 MySQL