天天看点

Qt绘制曲线

引言

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

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

1 坐标转换

(1)坐标系的认识

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

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

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

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

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

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

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

(2)坐标系的转换

屏幕坐标–>窗口坐标​

Qt绘制曲线
// 首先应明确的是窗口坐标中的总长度winWidth和总高度winHeigth。
winWidth = this->width();
winHeight = this->height();

// 那屏幕坐标最好在左右预留出xOff ,在上下预留出yOff 。也是对称美,也是为了标出x和y上的刻度。屏幕坐标的总长度scWidth和总高度scHeight也就有了。
scWidth = winWidth-2*xOff; //事先定义的xOff ,yOff
scHeight = winHeight-2*yOff;
           

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

Qt绘制曲线

由上图可以得到:屏幕坐标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

,

yePoin

t均是屏幕坐标下的点,画直线时都要转换成窗口坐标才行。根据

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();

就完成了绘制。

绘制的直线和曲线如下:

Qt绘制曲线
Qt绘制曲线

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

Qt

继续阅读