先看程序输出的y=tan(x)的函数图:
y = tan(x)函数图
本程序的函数分析公式为y = tan(kx b),考虑一般情况,系数k可正可负。
绘图使用了Qt的图形视图框架(graphics view framework)。
绘图发生在一个矩形内,该矩形也被设置为场景的范围。 矩形的宽度与对应的X表示的范围的比值就是比例因子(代码中的mySCALE)。 例如,如果用一个宽度为1000像素的矩形表示[-100,100]的X区间,那么缩放系数就是5。
程序的两个主要模块是计算模块 函数值tangentCal(qreal k, qreal b),以及函数图像的绘图模块drawGraph(QVectorvec, qreal k, QPen pen) .
头文件:
//////////////////////////mainwindow.h///// ////////////////////////////////
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include
#include
#include
QT_BEGIN_NAMESPACE
命名空间 Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void drawSine(qreal k,qreal b,QPen pen) ; // 绘制正弦函数图像
void drawCosine(int k,QPen pen); //绘制余弦函数图像
void drawAxis(); //绘制坐标系
void drawtangent(int period,QPen pen);
void drawTangentkx(qreal k,qreal b,QPen pen);
QVector tangentCal(qreal k,qreal b) ;
void drawGraph(QVector vec, qreal k,QPen pen);
private:
Ui::MainWindow *ui;
QGraphicsScene *scene;
QPainterPat h m_path; //用来保存函数image绘制的路径
QLineF Domain; //一个容器,用来表示y =sin(kx b)函数的定义域,里面包含了一条直线的函数图像的起点和终点。 注意这里的直线一定要用QLineF,不能用QLine。
int nSteps; //用来表示y =sin(kx b)函数一次循环采样的点数
/ *这个SCALE是一个缩放比例,等于像素个数的比例 在绘图区域的水平方向到功能域的长度。
比如你用一个宽度为400像素的区域作为定义域为2*π函数的绘图区域,那么这个
比例就等于400/( 2*π). 即2*π的数学函数域,对应物理
绘图区域宽度为400像素。 */
qreal SCALE;
const qreal PI = 3.14159265358979;
int n; //函数域:[-n*π,n*π ],本例中n取3
QPointF m_start; //函数图像的起点。 注意不要使用QPoint,因为精度不够。
QPointF m_end; //函数结束点图像
};
#endif // MAINWINDOW_H
part 1 y = tan( kx b) 函数值计算模块
因为正切函数的取值范围是从负无穷大到正无穷大。 因此,(-π/2, π/2)中的正切函数的图像与表示绘制区间的矩形会有两个交点,对应的横坐标分别命名为x1,x2。 在这个区间内,取几个点,计算出点的坐标,用QVector容器保存这一系列点的坐标。
由于系统默认Y轴向下,所以函数值取反,与我们习惯的Y轴一致。
//[x1,x2]是y=tan(kx b)域中连续区间中的一个子区间,对应于(-π/2, π/ of y = tan(x) 2)
//这里计算出的x1和x2对应于正切函数与绘制图形矩形区域交点的横坐标上下两边。
//该函数计算正切函数y = tan(kx b)的值,形成QPointF(x,y),保存在QVector容器中。
QVector MainWindow::tangentCal(qreal k,qreal b)
{
//物理绘图空间与数学坐标之间的缩放比例
p>
qreal myScale = scene->sceneRect().width()/Domain.length();
qreal yMax = scene->sceneRect().height()/2.0/ myScale; //5.65487, yMax in QRect(-500,-300,1000,600)
qreal yMin = -yMax;
qreal x1,x2;
//根据k的不同符号,当k<0时,函数的绘图区间[x1,x2]的起点和终点会被调整。
if(k>0)
{
x1 = (atan(yMin)-b)/k;
x2 = (atan(yMax)-b)/k;
}
else
{
x2 = (atan(-yMax) -b)/k;
x1 = (atan(yMax)-b)/k;
}
qreal bound = x2-x1;
p>
QVector vec;
无符号短 n = 30; //将[x1,x2]分成30个子区间
qreal step = bound/n ;
for(int i=0;i<=n;i )
{
qreal x = x1 i*step;
p>qreal y = -tan(k*x b);
vec< <QPointF(x,y);
}
返回vec;
返回vec;
p>
}
part 2 y = tan(kx b)函数图像绘制模块
绘制部分主要使用QPainterPath类型的对象m_path来记录绘制轨迹,然后将轨迹添加到场景中。 我们知道,正切函数的函数图像是不连续的,分为很多圈。 画完一个时期的函数图像后,左右移动就可以得到其他时期的图像。
//绘制y = tan(kx b)的函数图像
void MainWindow::drawGraph(QVectorvec,qreal k, QPen pen)
p>{
qreal myScale = scene->sceneRect().width()/Domain.length();
m_path.clear();
m_path.moveTo((vec.at(0)) *myScale); //将一个点的x和y坐标同时放大myScale
for(int i = 1;i<= vec.size ()-1;i )
m_path.lineTo(vec.at(i)*myScale);
//给场景添加切线函数图像
scene->addPath(m_path,pen); //(kx b)作为自变量,这张图对应的是基本周期(-π/2,π/2)。
/*在[-n*π,n*π]范围内,除了基本周期外,还有2*abs(n*k)张图片可以平移生成。
k可正可负,所以这里的n*k要取绝对值。 */
int num = abs(n*k);
for(int i = 1;i<=num-1;i )
{
//将切线函数图像向右平移以在新的循环中创建图像
scene->addPath(m_path.translated(QPointF(PI/k*i,0 ) *myScale), pen);
//将切线函数图像向左平移以在新循环中创建图像
scene->addPath(m_path.translated(- QPointF( PI/k*i,0)*myScale),pen);
}
}
y = tan(-x)的函数图像 (红色部分)
y = tan(2x-π/8)函数图(红色部分)
总结:
本文学习了如何使用 Qt图形视图框架绘制正切函数的图像。 通过画图,高中生可以加深对正切函数的图像特征的理解。 程序中用到的知识点包括:
(1)Qt的图形视图框架;
(2)正切函数的数学知识(高中),其中使用基本区间( -π/2, π/2) 内部函数的区间x1, x2, 在给定的X轴区间内计算了多少个循环,循环的计算公式等
(3 ) 使用QVector容器保存计算出的点坐标QPointF。 注意不能使用QPoint,因为误差从数学函数值到物理绘图设备也被放大了,绘图精度不够。
(4)使用QPainterPath保存函数图像的绘制路径。 然后将其添加到场景中,再将场景与视图关联起来,就可以在视图中看到场景容器中的函数图像了。