现在的位置: 首页 > 综合 > 正文

如何绘制动态曲线

2014年09月05日 ⁄ 综合 ⁄ 共 12039字 ⁄ 字号 评论关闭

1 QWT绘制动态曲线

引子

      项目终于快完成了,现在开始整理下代码,总结下,今天先分享下关于使用如何用QWT绘制波形,先把图给贴出来。

绘制AD数据

关于QWT

     以下源于百度百科“QWT全称是Qt Widgets for Technical Applications,是一个基于LGPL版权协议的开源项目,可生成各种统计图。它为具有技术专业背景的程序提供GUI组件和一组实用类,其目标是以基于2D方式的窗体部件来显示数据, 数据源以数值,数组或一组浮点数等方式提供,输出方式可以是Curves(曲线),Slider(滚动条),Dials(圆盘),Compasses(仪表盘)等等。该工具库基于Qt开发,所以也继承了Qt的跨平台特性。”

    我使用的是qwt-6.0.0-rc5,可以到官网下载http://qwt.sourceforge.net/,具体的安装和集成过程可以参照http://www.cuteqt.com/blog/?p=994这个贴子。

源码

“AdPlot.h”

1: #ifndef ADPLOT_H
2: #define ADPLOT_H
3: 
4: #include <QWidget>
5: #include <qwt_plot_curve.h>
6: #include <qfile.h>
7: #include <qwt_plot_magnifier.h>
8: 
9: namespace Ui {
10: class AdPlot;
11: }
12: 
13: class AdPlot : public QWidget
14: {
15: Q_OBJECT
16: 
17: public:
18: explicit AdPlot(QWidget *parent = 0);
19: //要绘制的AD数据,X-时间,Y-电压值
20: void readAdData(QVector< double > &xData,
21: QVector< double > &yData);
22: void setTitleString(QString title);
23: void setTimerStop();
24: 
25: ~AdPlot();
26: public slots:
27: //节点打开的AD数据的文件名
28: void setFileName(QString filename);
29: //绘制AD数据
30: void plotAdCurve();
31: private:
32: Ui::AdPlot *ui;
33: QwtPlotCurve *p_adplot;
34: QVector< double > xData;
35: QVector< double > yData;
36: QFile *localFile;
37: //文件偏移量
38: qint64 offset;
39: double time;
40: QString m_filename;
41: QwtPlotMagnifier *PM;
42: //当前X轴最大的范围
43: int xMaxScale;
44: 
45: QTimer *adPlotTimer;
46: 
47: QString m_title;
48: };
49: 
50: #endif // ADPLOT_H

 

    头文件没什么好说的,说明一下offset这个变量,它是用来保存当前的读取到的文件位置,而xData和yData分别储存时间和电压值。

“AdPlot.cpp”

1: #include "adplot.h"
2: #include "ui_adplot.h"
3: #include <QtGui/QApplication>
4: #include <qapplication.h>
5: #include <qlayout.h>
6: #include <qlabel.h>
7: #include <qpainter.h>
8: #include <qwt_plot_layout.h>
9: #include <qwt_plot_curve.h>
10: #include <qwt_scale_draw.h>
11: #include <qwt_scale_widget.h>
12: #include <qwt_legend.h>
13: #include <qwt_legend_item.h>
14: #include <QTime>
15: #include <qtimer.h>
16: #include <QMessageBox>
17: #include <qcolor.h>
18: #include <qwt_plot_zoomer.h>
19: 
20: class TimeScaleDraw: public QwtScaleDraw
21: {
22: public:
23: TimeScaleDraw(const QTime &base):
24: baseTime(base)
25: {
26: }
27: 
28: virtual QwtText label(double v) const
29: {
30: QTime upTime = baseTime.addSecs((int)v);
31: return upTime.toString();
32: }
33: 
34: private:
35: QTime baseTime;
36: };
37: 
38: 
39: AdPlot::AdPlot(QWidget *parent) :
40: QWidget(parent),
41: ui(new Ui::AdPlot)
42: {
43: ui->setupUi(this);
44: ui->qwtPlot->setAutoReplot(false);
45: QTime curTime;
46: curTime = curTime.currentTime();
47: 
48: 
49: 
50: QwtLegend *legend = new QwtLegend;
51: legend->setItemMode(QwtLegend::CheckableItem);
52: //ui->qwtPlot->insertLegend(legend, QwtPlot::RightLegend);
53: ui->qwtPlot->setAxisTitle(QwtPlot::xBottom, " System Uptime [h:m:s]");
54: ui->qwtPlot->setAxisScaleDraw(QwtPlot::xBottom,
55: new TimeScaleDraw(curTime));
56: ui->qwtPlot->setAxisScale(QwtPlot::xBottom, 0, xMaxScale = 5);
57: ui->qwtPlot->setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom);
58: 
59: QwtScaleWidget *scaleWidget = ui->qwtPlot->axisWidget(QwtPlot::xBottom);
60: const int fmh = QFontMetrics(scaleWidget->font()).height();
61: scaleWidget->setMinBorderDist(0, fmh / 2);
62: 
63: ui->qwtPlot->setAxisTitle(QwtPlot::yLeft, "V");
64: ui->qwtPlot->setAxisScale(QwtPlot::yLeft, 0, 3);
65: p_adplot = new QwtPlotCurve();
66: QColor c = Qt::red;
67: c.setAlpha(150);
68: p_adplot->setPen(c);
69: 
70: p_adplot->attach(ui->qwtPlot);
71: //PM = new QwtPlotMagnifier(ui->qwtPlot->canvas());
72: QwtPlotZoomer* zoomer = new QwtPlotZoomer( ui->qwtPlot->canvas() );
73: zoomer->setRubberBandPen( QColor( Qt::black ) );
74: zoomer->setTrackerPen( QColor( Qt::black ) );
75: zoomer->setMousePattern(QwtEventPattern::MouseSelect2,Qt::RightButton, Qt::ControlModifier );
76: zoomer->setMousePattern(QwtEventPattern::MouseSelect3,Qt::RightButton );
77: time = 0.0;
78: adPlotTimer = new QTimer();
79: adPlotTimer->start(100);
80: connect(adPlotTimer, SIGNAL(timeout()),
81: this, SLOT(plotAdCurve()));
82: offset = 0;
83: }
84: 
85: AdPlot::~AdPlot()
86: {
87: delete ui;
88: }
89: 
90: void AdPlot::readAdData(QVector<double> &xData, QVector<double> &yData)
91: {
92: if(!m_filename.isEmpty())
93: {
94: localFile = new QFile(m_filename); //用户端的IP地址+端口号作为文件名
95: if (!localFile->open(QFile::ReadOnly ))
96: {
97: QMessageBox::warning(0, tr("应用程序"),
98: tr("无法读取文件 %1:\n%2.")
99: .arg(m_filename)
100: .arg(localFile->errorString()));
101: return;
102: }
103: QByteArray adData;
104: if (!localFile->atEnd())
105: {
106: if (offset == 0)
107: {
108: adData = localFile->read(26);
109: qDebug()<<adData;
110: offset += 26;
111: adData.resize(0);
112: return;
113: }
114: 
115: if (localFile->seek(offset))
116: {
117: 
118: adData = localFile->read(32*2);
119: if (adData.count() == 32*2)
120: {
121: for (int count=0; count<32; count++)
122: {
123: char lowbits = adData.at(count*2);
124: char highbits = adData.at(count*2 + 1);
125: short temp = lowbits+highbits*256;
126: yData.append(((float)temp)*3300/4096/1000);
127: xData.append(time);
128: time += 0.005; //5ms一个数据
129: }
130: offset += 32*2;
131: }
132: }
133: }
134: }
135: 
136: }
137: 
138: void AdPlot::plotAdCurve()
139: {
140: readAdData(xData,yData);
141: p_adplot->setSamples(xData,yData);
142: if (!xData.empty())
143: {
144: if (xData.last() >= xMaxScale)
145: {
146: xMaxScale *= 2;
147: ui->qwtPlot->setAxisScale(QwtPlot::xBottom, 0, xMaxScale);
148: }
149: }
150: p_adplot->attach(ui->qwtPlot);
151: ui->qwtPlot->replot();
152: adPlotTimer->start(100);
153: 
154: 
155: }
156: 
157: void AdPlot::setFileName(QString filename)
158: {
159: m_filename = filename;
160: }
161: 
162: void AdPlot::setTitleString(QString title)
163: {
164: m_title = title;
165: ui->qwtPlot->setTitle(m_title);
166: }
167: 
168: void AdPlot::setTimerStop()
169: {
170: adPlotTimer->stop();
171: }

首先:

说一下TimeScaleDraw这个类,它继承了QwtScaleDraw的类,其实就是一个抽象的一个刻度类,具体的函数说明可以参考说明手册.

对了,ui界面里没什么东西,就一个qwtplot的控件image.

 

接着初始化坐标轴:

  1. void setAxisTitle (int axisId, const QString &) 用来设置坐标轴名字,第一个参数是QWT定义的坐标轴ID,即yLeft,yRight,xBottomxTop.
  2. void setAxisScaleDraw (int axisId, QwtScaleDraw *)用来设置坐标轴刻度,第一个参数同上,第二个参数就是我们刚才定义的TimeScaleDraw;
  3. void setAxisScale (int axisId, double min, double max, double step=0)也是设置刻度的,第一个参数同上,第二参数是刻度的最小值,第三个参数是刻度的最大值,最后一个是步长.

 

然后定义一个曲线QwtPlotCurve:

     QwtPlotCurve,它的功能就是输入一系列的X,Y的值,然后绘制出曲线,当然,曲线的color,style那些是可以自己设置的.

 

缩放曲线(PS:这点就体现了QWT的强大之处):

     QwtPlotZoomer,简单的一个类,简单的只个设置就可以缩放曲线,谁用谁知道啊.

 

定时器完成曲线的重绘:

      adPlotTimer = new QTimer();

      adPlotTimer->start(100);

      connect(adPlotTimer, SIGNAL(timeout()), this, SLOT(plotAdCurve()));

      因为所用的AD是400K的,算了一下,大概160ms以下就能满足了.

 

读取AD数据:

     因为服务器接收到的AD数据是保存在文件中的,而且是实时保存(也就是将Client接收的数据保存到文件的同时将此文件中的数据绘制出来).所以设置了一个文件偏移量,而已此文件的前26字节是采集的时间信息,所以跳过.

     又因为用的是AD是12位的,Client采用一个short类型来保存数据,而已每次传输32个AD数据,所以在计算AD数据前要确保文件已经有了32个AD数据,即64字节的数据.

 

绘制AD数据:

     void setSamples (const QVector< double > &xData, const QVector<
double > &yData)完成数据的设置.

     此函数重点在于时间轴的拉长:

     if (xData.last() >= xMaxScale)

{
xMaxScale *= 2;
ui->qwtPlot->setAxisScale(QwtPlot::xBottom, 0, xMaxScale);
}
 

总结:

     qwt的确一个优秀的第三方库,使用也不复杂,而已有详细的文档说明,还有很多例子,不过都是英文的,慢慢看吧.

转;http://blog.sina.com.cn/s/blog_7cb9d2f90100rr9v.html

==================================================================================

2   Qwt显示动态实时曲线

从传感器实时获取的数据,从串口上传到上位机,上位机由QT开发,上位机如何通过实时动态曲线方式进行展示呢?网上主要有两种方式:1、使用qwt;2、QCustomPlot;两者都差不多,QCustomPlot貌似更灵活漂亮点,但是由于qwt还有其他控件,这次的选择是qwt。

Java代码  收藏代码
  1. /***************************************  
  2. 转载请注明出处:tedeum.iteye.com  
  3. ****************************************/    

一、首先要定义和实例化一个QwtPlot,然后是一根曲线QwtPlotCurve,还有就是数据,由于QwtPlot是从设计器拉到界面,IDE做了这个工作就暂时不管了,如果没有IDE可以用代码写的:

Cpp代码  收藏代码
  1. //曲线     
  2.  QwtPlotCurve * curve;  
  3. //X轴  
  4. double time[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};  
  5. //Y轴  
  6. double val[10] = {3, 5, 8, 7, 2, 0, 7, 9, 1};  

 二、接下来就是把这些东西组装起来:

Cpp代码  收藏代码
  1. //实例化  
  2. curve = new QwtPlotCurve("Acc_X");  
  3. //加载数据  
  4. curve->setSamples(time, val, 10);  
  5. //加到plot,plot由IDE创建  
  6. curve->attach(ui->qwtPlot);  

 话不多说,一切尽在注释中,运行可以看见曲线绘制出来了:

图表显示出来,现在的问题是这是一个静态图标,如何动态显示实时数据呢,我们用定时器模拟产生随机数进行动态数据的展示,如下:

Cpp代码  收藏代码
  1.      //启动定时器,1秒响应,用于模拟产生实时数据  
  2.      this->startTimer(1000);  
  3. //定时器事件  
  4. void MainWindow::timerEvent( QTimerEvent * ) {  
  5.     //所有数据前移移位,首位被覆盖  
  6.     for (int i = 0; i < 9; i++) {  
  7.         val[i] = val[i+1];  
  8.     }  
  9.     //最后一位为新数据(这里为随机数模拟)  
  10.     val[9] = qrand()%10;  
  11.     //重新加载数据  
  12.     curve->setSamples(time, val, 10);  
  13.     //QwtPlot重绘,重要,没有这句不起作用  
  14.     ui->qwtPlot->replot();  
  15.   
  16. }  

 好了,到此为止可以动态显示实时曲线了:




 最后,还有一个问题当多条曲线共同显示的时候,如何隐藏显示曲线呢,比如加速度传感器的x、y、z共同显示,我只想看一条。可以通过点击图例在做到,好了还是看代码和注释:

Cpp代码  收藏代码
  1.      //实例化图例  
  2.      QwtLegend *legend = new QwtLegend;  
  3.      //图例可以选择,Checkable  
  4.      legend->setDefaultItemMode( QwtLegendData::Checkable );  
  5.      //pwtPlot中插入图例  
  6.      ui->qwtPlot->insertLegend(legend, QwtPlot::LeftLegend );  
  7.      //连接槽,处理选择事件  
  8.      connect( legend, SIGNAL( checked( const QVariant &, boolint ) ),  
  9.          SLOT( legendChecked( const QVariant &, bool ) ) );  
  10. //图例选择事件  
  11. void MainWindow::legendChecked( const QVariant &itemInfo, bool on )  
  12. {  
  13.     //获取曲线  
  14.     QwtPlotItem *plotItem = ui->qwtPlot->infoToItem( itemInfo );  
  15.     //根据图例选择状态,设置曲线隐藏还是显示  
  16.     if ( plotItem )  
  17.         plotItem->setVisible(on);  
  18.     //重要,下面这句没有不起作用  
  19.     ui->qwtPlot->replot();  
  20. }  

 好了,简单吧。qwt实时曲线显示的关键技术细节都在这里了:


转:http://tedeum.iteye.com/blog/2018706

==================================================================

3   绘制动态曲线

在工控监测领域,经常需要动态绘制曲线,观察曲线的变化趋势,绘制波形图,绘制频谱等。在前面4讲中介绍了MFC经常用的TeeChart控件和Hight-Speed
Chart
 Ctrl,这两个都是MFC绘图控件的经典(另外,在Qt中还有QwtPlotQCustomPlot两大神器)。许多人问如何绘制动态变化的曲线,为此专门写下这篇文章。


C++ GUI 绘图控件目录

MFC

Qt

















对于任何绘图控件,都可以实现动态绘图,其原则是:控件只负责绘图,若想曲线动,就让数据动,就像看电影一样,电影是由一帧一帧的静态图片组合起来的,在一定速度上刷新,静态图片就能动起来;和电影的原理一样,绘图控件能显示静态的曲线,想要它动起来,就让它频在一定时间刷新就可以了。

这就是动态绘图的实现原理。

实现动态曲线需要以下两个准备:

  1. 计时器Timer
  2. 数组左移

基于Timer的绘图

任何界面库都会有Timer这个实现,在MFC中时OnTimer消息,在Qt中是QTimer类,那种原理基本都一样,下面将以MFC为例进行说明。

Timer是消息级别最低的消息,它会保证其它级别高的消息优先执行,因此,就算数据大量刷新,也不会影响主线程的其它消息。

MFC生成OnTimer消息,消息响应函数如下:

  1. void CTeeChartDlg::OnTimer(UINT_PTR nIDEvent)  
  2. {  
  3.     // TODO: 在此添加消息处理程序代码或调用默认值  
  4.     CDialogEx::OnTimer(nIDEvent);  
  5. }  

绘图的实现就在这个消息响应函数里

如果让定时器设定为1秒触发,每一秒把旧数据去除,绘制新数据,就能看到不停变换的波形;对于趋势图,假如每秒有一个新数据,那么就在定长数组中,把数组所有数据整体左移,同时数组末端加入新数据。代码如下:

  1. ///   
  2. /// \brief 左移数组  
  3. /// \param ptr 数组指针  
  4. /// \param data 新数值  
  5. ///  
  6. void LeftMoveArray(double* ptr,size_t length,double data)  
  7. {  
  8.     for (size_t i=1;i<length;++i)  
  9.     {  
  10.         ptr[i-1] = ptr[i];  
  11.     }  
  12.     ptr[length-1] = data;  
  13. }  

此函数把整个数组左移,然后新数据放置在数组最末端(右端)。

这样,数组就实现“向左运动”,把左移后的数组绘制,就能在绘图控件上发现其变化。

下面开始实现动态绘图(这里演示TeeChart的方法,附件里有HightSpeed-Chart CChartCtrl的方法):

  1. void CTeeChartDlg::OnBnClickedButtonRuning()  
  2. {  
  3.     KillTimer(0);  
  4.     ZeroMemory(&m_TeeChartArray,sizeof(double)*m_c_arrayLength);  
  5.     for (size_t i=0;i<m_c_arrayLength;++i)  
  6.     {  
  7.         m_X[i] = i;  
  8.     }  
  9.     m_count = m_c_arrayLength;  
  10.     CSeries chart_T = (CSeries)m_Chart.Series(0);  
  11.     chart_T.Clear();  
  12.     m_pLineSerie->ClearSerie();  
  13.     SetTimer(0,1000,NULL);    
  14. }  

函数中几个成员变量的定义是:

  1. double m_TeeChartArray[2096];  
  2. double m_X[2096];  
  3. unsigned int m_count;  
  4. const size_t m_c_arrayLength = 2096;  

m_TeeChartArray是需要绘制的数组的Y值,m_X是对应的x值,m_count是计数器,每绘制一次,个数加1,主要用于x轴

在timer中的实现如下:

  1. void CTeeChartDlg::OnTimer(UINT_PTR nIDEvent)  
  2. {  
  3.     // TODO: 在此添加消息处理程序代码和/或调用默认值  
  4.     if(0 == nIDEvent)  
  5.     {  
  6.         ++m_count;  
  7.         drawMoving();  
  8.     }  
  9.     CDialogEx::OnTimer(nIDEvent);  
  10. }  

drawMoving函数用于绘图,timer设定为1秒触发一次,这时就能看到每秒的变化,如果数据是以1秒为刷新周期,每一秒有个新数据,只需要把旧的数据向左移,新数据放到数组最右端,再在绘图控件上把此图形画出来即可看的像动一样。

drawMoving函数的实现如下:

  1. void CTeeChartDlg::drawMoving()  
  2. {  
  3.     CSeries chart_T = (CSeries)m_Chart.Series(0);  
  4.     chart_T.Clear();  
  5.     m_pLineSerie->ClearSerie();  
  6.     LeftMoveArray(m_TeeChartArray,m_c_arrayLength,randf(0,10));  
  7.     LeftMoveArray(m_X,m_c_arrayLength,m_count);  
  8.     DrawLine_TeeChart(m_X,m_TeeChartArray,m_c_arrayLength);  
  9. }  

前面说过timer是优先级最低的消息,如果想曲线动的流畅,可以把时钟设置为0ms,如

  1. SetTimer(0,0,NULL);   

这时会在保证界面流畅的前提下,以最高频率刷新。这样看到的图形会非常流畅。

上面介绍的就是动态绘制曲线的思路和方法,附件中有用TeeChart实现和HightSpeedChart实现的例子,考虑到可能有些人没有安装TeeChart,专门把TeeChart分离出来了一个源码,只有HightSpeedChart,不需要安装任何控件。


demo1:

MFC下TeeChart和HightSpeedChart动态绘制曲线图-VS2010

demo2(不用安装任何控件):

MFC动态绘制曲线图-HightSpeedChart实现

转;http://blog.csdn.net/czyt1988/article/details/20136895

抱歉!评论已关闭.