前言:
这里主要是做出了一个动态的正弦曲线,并可以调整正弦曲线的振幅和周期。网上有很多画动态正弦曲线的,有一些是通过绘制一整个正弦图像,然后每一次显示都是刷新一整个正弦曲线来达到动态的效果,但是有时候需要的效果可能不是这样。
这里的正弦曲线主要是通过在数据容器中每次追加一个曲线点来达到动态的效果。当当前的坐标轴已经画满了曲线,那么就更新当前的坐标系,将当前坐标轴往前移动一个单位( x 轴 坐标 +1),这样整个坐标系就会看到往前移动了,显示出动态的效果。而动态地改变周期和振幅,则是通过按钮来改变,当点击一下对应的按钮时,就根据当前的振幅和周期,重新生成正弦曲线点,然后把改变前的数据擦除,重新追加新的数据进数据容器中,再重新绘图显示,这样就能改变图像的周期和振幅了。
下面是效果图:
一、准备好调整Sin曲线的接口
首先给出产生正弦曲线数据点的代码,这里的代码我在linux上用纯C写,把它编译成库,供Qt调用
/* SinData.h */
#ifndef SINDATA_H
#define SINDATA_H
#include <stdio.h>
#include <math.h>
#include <pthread.h>
#include <unistd.h>
void StartSin(); //启动线程
int GetSinData(int *AxisX,int *AxisY); //获取Sin坐标点
void SetAmplitude(int Amplitude); // 设置振幅
void SetCycle(int Cycle); // 设置周期
void SetSinOffset( int SinOffset); // 设置Y轴偏移量
void UpdateSinData(int AxisX[],int AxisY[]); // 更新Sin数据
#endif
/* SinData.c */
#include "SinData.h"
#define PI 3.1415926
#define BUFFER_SIZE 1024
int g_Buffer[BUFFER_SIZE];
int g_BufferIndex;
int g_AxisX;
int g_AxisY;
int g_AxisIndex;
int g_Amplitude = 25;
int g_Cycle = 6;
int g_SinOffset = 135;
/*
Function Name : SetAmplitude
Description : 设置振幅
Input Parameters: Amplitude :振幅值
*/
void SetAmplitude(int Amplitude)
{
g_Amplitude = Amplitude;
}
/*
Function Name : SetCycle
Description : 设置周期
Input Parameters: Cycle :周期
*/
void SetCycle(int Cycle)
{
g_Cycle = Cycle;
}
/*
Function Name : SetSinOffset
Description : 设置偏移量
Input Parameters: SinOffset :y轴的偏移量
*/
void SetSinOffset( int SinOffset)
{
g_SinOffset = SinOffset;
}
/*
Function Name: RecvData
Description : 设置完参数后,用来重新生成数据,更新Sin图像
*/
void UpdateSinData(int AxisX[],int AxisY[])
{
int m;
for(m = 0; m <g_AxisIndex ; m++)
{
AxisX[m] =m;
AxisY[m] = g_Amplitude * sin( PI / g_Cycle * (AxisX[m])) + g_SinOffset;
}
}
/*
Function Name: void DrawSin
Description : 产生sin图像的坐标
*/
void DrawSin()
{
g_AxisY = g_Amplitude * sin( PI / g_Cycle * g_AxisIndex )+ g_SinOffset;
g_AxisX = g_AxisIndex;
g_AxisIndex++;
}
/*
Function Name : GetSinData
Description : 获取sin图像的坐标,供外部调用
Input Parameters: AxisX :x轴坐标 AxisY:y轴坐标
Return Value : 1 :成功获取
0 :获取失败(接收数据的缓冲区没满)
-1:获取失败
*/
int GetSinData(int *AxisX,int *AxisY)
{
if( g_BufferIndex == BUFFER_SIZE)
{
DrawSin();
*AxisX = g_AxisX;
*AxisY = g_AxisY ;
static int s_Tick = 0;
s_Tick++;
g_BufferIndex = 0;
return 1;
}
return 0;
}
/*
Function Name: RecvData
Description : 模仿接收1K的数据
*/
void RecvData()
{
while(1)
{
if(g_BufferIndex == BUFFER_SIZE)
return;
g_Buffer[g_BufferIndex] = g_BufferIndex ;
g_BufferIndex++;
}
}
void *RecvDataPthread(void *arg)
{
while(1)
{
RecvData();
}
}
/*
Function Name : StartSin
Description : 启动线程,不停地获取数据
*/
void StartSin()
{
pthread_t pid;
pthread_create(&pid,NULL,RecvDataPthread,NULL);
}
以上代码只是用来产生Sin曲线的数据点,并提供接口去改变Sin曲线的振幅和周期,这里我是在linux下用C语言编写的,把它编译成So库,供Qt调用。
gcc SinData.c -o SinData.o -c -lm -lpthread
gcc -fPIC -shared SinData.o -c libsin.so
这样就生成了动态库。
接着在Qt的.pro文件中加上
INCLUDEPATH += /mnt/hgfs/shareFile/newTask // SinData.h的 绝对路径
LIBS += -L/mnt/hgfs/shareFile/newTask -lsin // libsin.so 的绝对路径
并且在Qt中的头文件中添加
extern "C"{
#include "SinData.h" // 表示需要使用到C中的头文件
}
这样产生Sin曲线的库已经准备好了,在SinData.h里面用来产生数据点的函数接口就可以使用了。
二、在Qt上绘制Sin图像
在Qt上绘制Sin图像,可以选择使用QCustomPlot和Qwt,在这里我选择使用Qwt,虽然Qwt的安装相比QCustomPlot来说比较复杂(QCustomPlot直接把.h 和.cpp文件放在工程即可),但是Qwt却更加好用。其实Qwt安装也不是很复杂,这里有个教程,几分钟就可以编译安装完毕,linux下安装Qwt详细步骤。
装好Qwt后,就开始画曲线了。
/**************************************************************************
Function Name: InitSinGrid
Description: 初始化网格
**************************************************************************/
void Widget::InitSinGrid()
{
m_FHRQwtPlot = new QwtPlot(this);
m_FHRQwtPlot->move(10,5);
m_FHRQwtPlot->setFixedWidth(780); // 设置坐标轴的大小
m_FHRQwtPlot->setFixedHeight(210);
m_FHRQwtPlot->setTitle(" "); // 设置坐标轴的标题
m_FHRQwtPlot->setAxisScale(QwtPlot::yLeft,30,240,30); // 坐标轴的范围和间隔
m_FHRQwtPlot->setAxisScale(QwtPlot::yRight,30,240,30);
m_FHRQwtPlot->setAxisScale(QwtPlot::xBottom,0,100,20);
m_FHRQwtPlot->enableAxis(QwtPlot::yRight,true);
m_FHRQwtPlot->enableAxis(QwtPlot::xBottom,false); // 隐藏X轴
m_FHRQwtPlot->setStyleSheet("background:transparent;");//删除坐标轴背景
m_FHRQwtPlot->setAxisMaxMinor(QwtPlot::xBottom,4);
m_FHRQwtPlot->setAxisMaxMinor(QwtPlot::yLeft,3);//设置每一个大格有都少个小格
m_FHRQwtPlot->setAxisMaxMinor(QwtPlot::yRight,3);
m_FHRQwtPlot->setAxisScaleDraw(QwtPlot::xBottom,new TimeScaleDraw( QDateTime::currentDateTime()));// 横坐标显示当前时间(需要把X轴显示出来,才能显示出时间)
m_FHRQwtPlot->setCanvasBackground(Qt::white); // 设置背景白色
// 删除坐标轴间的间距
for ( int n = 0; n < m_FHRQwtPlot->axisCnt; n++ )
{
QwtScaleWidget *poScaleWidget = m_FHRQwtPlot->axisWidget( n);
if (poScaleWidget)
{
poScaleWidget->setMargin( 0 );
}
QwtScaleDraw *poScaleDraw = m_FHRQwtPlot->axisScaleDraw( n );
if ( poScaleDraw )
{
poScaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false );
}
}
m_FHRQwtPlot->plotLayout()->setAlignCanvasToScales( true );
//画网格
QwtPlotGrid *grid = new QwtPlotGrid();
grid->enableXMin(true);
grid->enableYMin(true);
grid->enableX(true);
grid->enableY(true);
grid->setMajorPen(QPen(Qt::gray,1,Qt::SolidLine)); // 大网格
grid->setMinorPen(QPen(QColor(186,186,186),0,Qt::DotLine));// 小网格
grid->attach(m_FHRQwtPlot);
// 往坐标系添加曲线
m_FHRCurve = new QwtPlotCurve;
m_FHRCurve->setPen(QColor(255,85,255),2,Qt::SolidLine);
m_FHRCurve->setCurveAttribute(QwtPlotCurve::Fitted,true); //圆滑显示曲线
m_FHRCurve->setRenderHint(QwtPlotItem::RenderAntialiased,true);
m_FHRCurve->attach(m_FHRQwtPlot); // 把曲线加载到坐标轴上
//m_UpdateFlag = 0;
//m_LimitBackGroud = new Background(m_HighLimit,m_LowLimit);//给绘图填充背景
//m_LimitBackGroud->attach( m_FHRQwtPlot );//插入plot
// 启动定时器
m_SinTimer = new QTimer();
connect(m_SinTimer,SIGNAL(timeout()),this,SLOT(TimeSlot()));
StartSin(); // 开始启动线程,产生Sin坐标点(上面SinData.h里面的接口函数)
m_SinTimer->start(500);
}
/**************************************************************************
Function Name: TimeSlot
Description: 定时器槽函数,用来绘制Sin图像
**************************************************************************/
void Widget::TimeSlot()
{
static int s_BeginAixsX = 0; // 起始横坐标
static int s_Result ;
if ( (s_Result =GetSinData(&m_SinPointX,&m_SinPointY)) <=0 ) // 获取Sin坐标点
{
return ;// 获取失败,直接返回
}
if(m_SinPointX >= 100) // 当当前坐标轴画满了,就把坐标轴往前移动,(更新X轴范围)
{
s_BeginAixsX++;
m_FHRQwtPlot->setAxisScale(QwtPlot::xBottom,s_BeginAixsX,m_SinPointX,20);// 更新坐标轴
m_DataVectorX.erase(m_DataVectorX.begin(),m_DataVectorX.begin()+1);
m_DataVectorY.erase(m_DataVectorY.begin(),m_DataVectorY.begin()+1);
}
m_DataVectorX.append((double)m_SinPointX);
m_DataVectorY.append((double)m_SinPointY);
m_FHRCurve->setSamples(m_DataVectorX,m_DataVectorY); // 绘图
m_FHRQwtPlot->replot(); // 重绘,一定要调用否则没效果
}
经过了上面的代码,此时Sin图像已经绘制成功了,并能显示出动态的效果,但是还没有调整振幅和周期。这个需要画两个QPushButton或者QToolButton,在点击信号对应的槽上来处理。当点击按钮时,Sin图像就会产生相应的变化。
//更改sin图像的Y轴偏移量、振幅、周期的槽函数。
void Widget::ChangeSinOffset()
{
m_UpdateFlag = DATA_UPDATA;
static int s_SinOffset = 120;
if(s_SinOffset == 160)
s_SinOffset = 120;
s_SinOffset +=10;
SetSinOffset(s_SinOffset);
UpdateData();
}
void Widget::ChangeAmplitude()
{
static int s_SinAmplitude = 20;
if(s_SinAmplitude == 50)
s_SinAmplitude = 20;
m_UpdateFlag = DATA_UPDATA;
s_SinAmplitude += 5 ;
SetAmplitude(s_SinAmplitude);
UpdateData();
}
void Widget::ChangeCycle()
{
static int s_SinCycle = 4;
if(s_SinCycle == 10)
s_SinCycle = 4;
m_UpdateFlag = DATA_UPDATA;
s_SinCycle +=1;
SetCycle(s_SinCycle);
UpdateData();
}
/**************************************************************************/
//Function Name: updateData
//Description: 更新sin图形的坐标,重绘
/**************************************************************************/
void Widget::UpdateData()
{
int axisX[m_SinPointX];
int axisY[m_SinPointX];
UpdateSinData(axisX,axisY); // SinData.h的接口函数
m_DataVectorX.clear();
m_DataVectorY.clear();
for(int n = 0 ; n <m_SinPointX ; n++)
{
m_DataVectorX.append((double)axisX[n]);
m_DataVectorY.append((double)axisY[n]);
}
if(m_SinPointX >100)
{
int len = m_DataVectorX.length() -100;
if( len < 0 )
len = 0-len;
//m_FHRQwtPlot->setAxisScale(QwtPlot::xBottom,len,m_SinPointX,20);
m_DataVectorX.erase(m_DataVectorX.begin(),m_DataVectorX.begin()+len);
m_DataVectorY.erase(m_DataVectorY.begin(),m_DataVectorY.begin()+len);
}
m_FHRCurve->setSamples(m_DataVectorX,m_DataVectorY);
m_FHRQwtPlot->replot();
m_UpdateFlag = 0;
}
这样,这个Sin图像就完成了,和效果图一致。动态显示Sin曲线,并可调整振幅和周期。