先看程序输出的 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(QVector<QPointF>vec,qreal k, QPen pen)。
头文件:
/////////////////////////////mainwindow.h///////////////////////////////////////
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPainterPath>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QLineF>
#include <cmath>
QT_BEGIN_NAMESPACE
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<QPointF> tangentCal(qreal k,qreal b);
void drawGraph(QVector<QPointF> vec, qreal k,QPen pen);
private:
Ui::MainWindow *ui;
QGraphicsScene *scene;
QPainterPath m_path; //用于保存函数图像绘的制路径
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)的定义域内一个连续区间内的子区间, 对应于y = tan(x)的(-π/2, π/2)
//这里求出的x1,x2分别对应正切函数与绘制图形的矩形区域在上下两条边的交点的横坐标。
//本函数计算正切函数y = tan(kx b)的值,组成QPointF(x,y),并放在QVector容器内保存。
QVector<QPointF> MainWindow::tangentCal(qreal k,qreal b)
{
//物理绘图空间与数学坐标之间的缩放比例尺
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的符号不同,函数的绘图区间[x1,x2]的起止点在k<0时会对调。
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;
QVector<QPointF> vec;
unsigned short n = 30; //把[x1,x2]划分成30个子区间
qreal step = bound/n;
for(int i=0;i<=n;i )
{
qreal x = x1 i*step;
qreal y = -tan(k*x b);
vec<<QPointF(x,y);
}
return vec;
}
part 2 y = tan(kx b)函数图像绘制模块
绘图部分,主要使用了QPainterPath类型的对象m_path, 记录绘制轨迹,然后把轨迹添加到场景中。我们知道,正切函数的函数图像是不连续的,分为很多个周期。绘制出一个周期的函数图像后,其他周期的图像,用左右平移的方法得到。
//绘制 y = tan(kx b)的函数图像
void MainWindow::drawGraph(QVector<QPointF>vec,qreal k, QPen pen)
{
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保存函数图像的绘制路径。然后添加到场景中,再把场景和视图关联,就可以在视图中看到场景容器中的函数图像了。
,