Qt绘制曲线

来源:互联网 发布:宏业软件下载 编辑:程序博客网 时间:2024/06/09 21:11

引言

应易和仓储系统需求,使运输车在行驶过程与刹车过程中运行得平稳,下位机通过无极变速控制应运而生,而上位机的无极变速参数设置也必不可少。这就用到了Qt的绘制曲线。

Qt的图形界面很厉害,之前的项目中用到的都是Qt的一些简单的应用,通过绘制曲线才对Qt的图形有了初步的了解。原来我也可以画出美丽平滑的余弦曲线。

1 坐标转换

(1)坐标系的认识

在绘制曲线之前,先要对坐标系有个深入的认识。如果对坐标系认识模糊,那么在绘制曲线时对于点的坐标也将是一团浆糊,更谈不上画出符合一定函数的曲线了。

在无极变速参数设置中,有三个坐标系,我把它们称为:世界坐标,屏幕坐标,窗口坐标。

世界坐标就是类似s(距离)--t(时间),v(速度)--t(时间)等坐标系。我们希望的是绘制出相应的函数曲线。

窗口坐标实际上是Qt的窗口坐标,显示的都是一个一个像素,单位是像素。另外,需注意的是Qt的窗口坐标是x向右y向下生长的,与数学中经常接触到的x向右y向上不太一样。

屏幕坐标是什么呢?可以认为是在窗口坐标中抠出一部分区域,在这个区域中绘制出肉眼看到的坐标系和曲线。单位也是像素。

但是Qt是只认识窗口坐标的。如何将无极变速中的速度(或频率),距离的函数关系描述出来,就需要将相应的世界坐标转换成相应的屏幕坐标,再将相应的屏幕坐标转换成窗口坐标显示。

当然,这3个坐标系只是当前应用所需,还可以继续推广。其他应用可能需要的坐标系转换更多,可能是5个(据说码垛机中的机器视觉识别就有5个坐标系)甚至更多。

(2)坐标系的转换

屏幕坐标-->窗口坐标​

 

首先应明确的是窗口坐标中的总长度winWidth和总高度winHeigth。

winWidth = this->width();

winHeight = this->height();

那屏幕坐标最好在左右预留出xOff ,在上下预留出yOff 。也是对称美,也是为了标出x和y上的刻度。屏幕坐标的总长度scWidth和总高度scHeight也就有了。

scWidth = winWidth-2*xOff; //事先定义的xOff ,yOff

scHeight = winHeight-2*yOff;

接下来就可以推算屏幕坐标和窗口坐标的对应关系了。

 

由上图可以得到:屏幕坐标sPoint与窗口坐标qPoint的关系是

//屏幕坐标转换为窗口坐标

void sc2qc(QPoint& sPoint,QPoint &qPoint)

{

qPoint.setX(sPoint.x()+xOff);

qPoint.setY(winHeight-yOff-sPoint.y());

}

世界坐标-->屏幕坐标

类似比例尺的缩放一样,将世界坐标缩放成屏幕坐标,x向y向自是有各自的比例系数kx,ky。如果世界坐标中x方向的范围是xRange ,y方向的范围是yRange 。

kx=scWidth/xRange;

ky=scHeight/yRange;

世界坐标中任意一点(xw,yw)转换成屏幕坐标中的一点sPoint时,乘以对应的比例系数即可。

void wc2sc(double xw,double yw,QPoint& sPoint)

{

double x =xw*kx;

double y = yw*ky;

//如果x和y都是double类型,就需要将其转换成int类型,因为Point类的x和y是像素,只能是int类型。那么double转换成int需要注意(我好像一直都没弄明白过)

if(x>=0)sPoint.setX(x+0.5);

else sPoint.setX(x-0.5);

if(y>=0)sPoint.setY(y+0.5);

else sPoint.setY(y-0.5);

}

至此,就搞清楚了3个坐标系中对应坐标的转换了。

2 绘制坐标轴

三个点,两条直线确定两个坐标轴,三个点xePoint, originPoint, yePoint均是屏幕坐标下的点,画直线时都要转换成窗口坐标才行。根据xePoint和yePoint可以确定两坐标轴端处的两个空心三角形。这样,坐标轴就大致画好了。

接下来绘制坐标轴上的刻度线。这也是个体力活,不过自从弄清楚坐标系的转换后,这都是个容易的事情。把屏幕坐标像素总长度scWidth平均分成几段(xSpaces),像素总高度scHeight平均分成几段(ySpaces),找到x向各个刻度线的坐标,y向各个刻度线的坐标,再drawLine()就可以。道理很简单,画起来有许多细节要留心。比如,不用一个一个刻度线去找坐标,只是两个循环;要将刻度线都画成虚线,看起来更漂亮,也是需要设置相应的格式的。

xGridPixel=scWidth/xSpaces;

yGridPixel=scHeight/ySpaces;

void FuncWidget::drawXGrid(int n)

{

//设置刻度线为虚线

painter->setPen(Qt::white);

painter->setPen(Qt::DashLine);

//刻度线上两点

QPoint xGridPoint(n*xGridPixel,0);

QPoint xGridTopPoint(n*xGridPixel,scHeight);

QPoint xGridPointQt;

QPoint xGridTopPointQt;

//屏幕坐标转换成窗口坐标

sc2qc(xGridPoint,xGridPointQt);

sc2qc(xGridTopPoint,xGridTopPointQt);

//绘制刻度线

painter->drawLine(xGridPointQt,xGridTopPointQt);

//写上刻度

QPoint textPoint=xGridPoint+QPoint(-20,-20);

QPoint textPointQt;

sc2qc(textPoint,textPointQt);

painter->drawText(textPointQt,tr("%1").arg(xRange/xSpaces*n));

}

for(int n=0;n<=xSpaces;n++) //xSpaces可随意设置

{

drawXGrid(n);

}

//y向也类似,就不一一列举。

3 绘制曲线

各种准备工作做完后,这才进入到刻画数学函数关系的时刻了。

先来缕清数学函数关系y=f(x) ,这里f 可以是刻画直线y=k*x+b的数学函数,也可以是刻画余弦曲线y=cos(x)的数学函数。依据自己应用需要而定。我要实现的正是直线和余弦曲线的绘制。

设置这样一个表示数学函数关系的函数,这句话中有两个函数,一个是数学中常用的数学函数,一个是程序语言中类的成员函数。这个成员函数为double f(int mode,int x);表示直线模式和曲线模式下由x得到y。

如果是直线函数,由k和b即得到y;如果是余弦函数,由标准库函数可得到x对应的余弦值或反余弦值。

取尽xRange范围内的所有x值,得到对应的所有的y值,会得到有限个对应数学函数关系的点,然后由x和y值会用到世界坐标向屏幕坐标的转换,得到转换后的点,依次连接这些点,就会绘制出对应的曲线。

QWidget有个信号槽函数update() ,当update()被调用时,会自动调用void paintEvent(QPaintEvent *)事件。所以绘制工作都在这个函数中实现。

在此之前,在构造函数中新建一个painter=new QPainter;再painter->begin(this);将坐标轴,刻度线,曲线都在其中绘制。最后painter->end();就完成了绘制。

绘制的直线和曲线如下:​

 

4 注意事项

1 double和int 的转换

double-->int :

可以用标准库中的round(x)函数,即取得x的四舍五入的整数值。

也可以如下:

double x;

int xi;

if(x>=0)xi=(x+0.5);

else xi=(x-0.5);

int-->double:

可以转换时在要转换的数前加double,也可以把数学符号前面或后面的某个数前做double转换。double w=2*(x-minHz); w-=1.0;

2 调用标准库函数

#include "stdio.h"

using namespace std;

#include "math.h"

double y=cos(x);


网址:http://blog.sina.com.cn/s/blog_9542b5c70102veg5.html

0 0