Qt自定义Plot实现曲线绘制的详细过程
时间:2022-02-17 09:29:23|栏目:Python代码|点击: 次
简介
实现了qt绘制曲线功能,包含arm触摸屏多点触控缩放(只支持两点),实时曲线绘制,数据点根据绘制宽度优化,跟踪点数据获取,双坐标等功能
演示
代码
头文件 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
链接: 源码下载