您可以自由地共享和演绎本文稿, 只需遵守开源协议[CC By 4.0]
如果这篇文章帮助到你, 可以请我喝一杯咖啡~
最近尝试了一个新的轻量级C/C++图形库EasyX,试着用它来开发一个可视化数据的项目。首先用它来试着写了一个简单绘制函数图像的小工具。
实现逻辑比较简单,即取若干样本点,将它们两两连成线。而实际编码较为啰嗦,因为需要根据给定函数确定图像的位置、比例等参数。
核心的绘图逻辑调用了库中的putpixel
和line
方法。
putpixel((int)xLoca, (int)yLoca, WHITE);
if (m == lineMode) line((int)xLoca, (int)yLoca, (int lastPair.first, (int)lastPair.second);
这两行代码是整个实现的核心,但支持它的部分一共达到了400余行。以我1.15
版本的程序为例:
如下的代码块是核心的绘图循环的部分:
for (double i = start; i - end < doubleErr; i +=step) {
stringstream SS;
SS << setiosflags(ios::fixed) << setprecision(2)<< "Drawing process: " << 100 * (i - start) /(end - start) << "%.";
outtextxy(5, 0, (LPCTSTR)SS.str().data());
double tempFunctionValue = functionRunnerY(i);
if (tempFunctionValue > YMax) YMax = tempFunctionValue;
if (tempFunctionValue < YMin) YMin = tempFunctionValue;
double tempXValue = functionRunnerX(i);
if (tempFunctionValue > XMax) XMax = tempFunctionValue;
if (tempFunctionValue < XMin) XMin = tempFunctionValue;
double xLoca = zeroPointX + tempXValue * unitX;
double yLoca = zeroPointY - tempFunctionValue *unitY;
if (j == 0) {
putpixel((int)xLoca, (int)yLoca, WHITE);
lastPair = { xLoca, yLoca };
}
else {
putpixel((int)xLoca, (int)yLoca, WHITE);
if (m == lineMode) line((int)xLoca, (intyLoca, (int)lastPair.first, (int)lastPairsecond);
slope = (yLoca - lastPair.second) / (xLoca -lastPair.first);
if (j != 1) {
slope = (yLoca - lastPair.second) /(xLoca - lastPair.first);
if ((slope / lastSlope >= 3.0 || slope /lastSlope <= 0.3) && abs(slope -lastSlope) > 1.0) {
differentiable = false;
}
}
lastSlope = slope;
if (abs(xLoca - lastPair.first) > 0.5 * ab(lastPair.first) && abs(yLoca - lastPairsecond) > 0.5 * abs(lastPair.second)) {
isLowGraph = true;
}
lastPair = { xLoca, yLoca };
}
j++;
stringstream EMPTY;
EMPTY << " ";
outtextxy(5, 0, (LPCTSTR)EMPTY.str().data());
}
这两个方法大同小异。其中,它调用了用来处理输入函数的方法functionRunner
:
double funcDraw::functionRunnerX(double x) {
double res;
if (_type == polar) {
try {
res = this->_functionY(x);
res = res * cos(x);
int dealTime = 0;
while (abs(res) > infLimit) {
double newPoint = x - infDeal;
infDeal *= -2;
res = this->_functionY(x);
res = res * cos(x);
dealTime++;
if (dealTime >= maxDealTime) {
pointErr err = { error::_OVERFLOW_, x };
throw(err);
}
}
}
catch (const std::exception) {
pointErr err = { error::_OVERFLOW_, x };
throw(err);
}
}
else {
try {
res = this->_functionX(x);
int dealTime = 0;
while (abs(res) > infLimit) {
double newPoint = x - infDeal;
infDeal *= -2;
res = this->_functionX(newPoint);
dealTime++;
if (dealTime >= maxDealTime) {
pointErr err = { error::_OVERFLOW_, x };
throw(err);
}
}
}
catch (const std::exception) {
pointErr err = { error::_OVERFLOW_, x };
throw(err);
}
if (abs(res - x) > doubleErr) _type = parametric;
}
return res;
}
double funcDraw::functionRunnerY(double x) {
double res;
if (_type == polar) {
try {
res = this->_functionY(x);
res = res * sin(x);
int dealTime = 0;
while (abs(res) > infLimit) {
double newPoint = x - infDeal;
infDeal *= -2;
res = this->_functionY(newPoint);
res = res * sin(x);
dealTime++;
if (dealTime >= maxDealTime) {
pointErr err = { error::_OVERFLOW_, x };
throw(err);
}
}
}
catch (const std::exception) {
pointErr err = { error::_OVERFLOW_, x };
throw(err);
}
}
else {
try {
res = this->_functionY(x);
int dealTime = 0;
while (abs(res) > infLimit) {
double newPoint = x - infDeal;
infDeal *= -2;
res = this->_functionY(newPoint);
dealTime++;
if (dealTime >= maxDealTime) {
pointErr err = { error::_OVERFLOW_, x };
throw(err);
}
}
}
catch (const std::exception) {
pointErr err = { error::_OVERFLOW_, x };
throw(err);
}
}
return res;
}
而为作图循环提供预处理的部分(求出比例、坐标零点位置等)的代码段是这样的:
if (m > 2) throw(error::_INVALID_MODE);
if (end < start) std::swap(end, start);
if (precision > (right - left)) throw(error::_TOO_BIG_PRE);
if (precision < 1) throw(error::_INVALID_PRE);
// if (_type == polar && ((start < -31.3 || end > 31.3))) throw(error::_INDE_OVERFLOW); 功能删除,注意:catch块内并未删除
cout << "Max thread numbers is: " << maxThread << "\n";
cout << "Preprocessing... \n";
infDeal = (end - start) / 500;
_minmaxs MaxMinX = this->preProcessX(start, end);
_minmaxs MaxMinY = this->preProcessY(start, end);
XMax = MaxMinX.first, XMin = MaxMinX.second;
YMax = MaxMinY.first, YMin = MaxMinY.second;
const double step = (_type == polar) ? max((double)precision * (end - start) / 100000, (double)precision / 100)
: (double)(XMax - XMin) * (double)precision / (double)(right - left);
if ((XMax - XMin) > 5 * windowLength || (YMax - YMin) > 5 * windowHeight)
isLowGraph = true;
double tempUnit;
if (XMin > 0) tempUnit = (right - left) / XMax;
else if (XMax < 0) tempUnit = (right - left) / -XMin;
else tempUnit = (right - left) / (XMax - XMin);
const double unitX = tempUnit;
if (YMin > 0) tempUnit = (down - up) / YMax;
else if (YMax < 0) tempUnit = (down - up) / -YMin;
else tempUnit = (down - up) / (YMax - YMin);
const double unitY = tempUnit;
double tempZeroPoint;
if (XMin > 0) tempZeroPoint = left;
else if (XMax < 0) tempZeroPoint = right;
else tempZeroPoint = -XMin * unitX + left;
const double zeroPointX = tempZeroPoint;
if (YMax < 0) tempZeroPoint = up;
else if (YMin > 0) tempZeroPoint = down;
else tempZeroPoint = down - (0 - YMin) * unitY;
const double zeroPointY = tempZeroPoint;
cout << "\bdone. \n";
this->drawUCS(zeroPointX, zeroPointY, unitX, unitY);
cout << "Drawing... ";
pair<double, double> lastPair;
int j = 0;
double slope, lastSlope;
其中调用了对于X
和Y
粗取样求近似最值的方法preProcess
:
funcDraw::_minmaxs funcDraw::preProcessX(const double start, const double end) {
double _max = INT_MIN, _min = INT_MAX;
const double step = (end - start) / 100;
for (double i = start; i < end; i += step) {
double temp = functionRunnerX(i);
int dealTime = 0;
if (temp > _max) _max = temp;
if (temp < _min) _min = temp;
cout << setiosflags(ios::fixed) << setprecision(0);
cout << (i - start) / (end - start) / 2 * 100 << "%";
std::cout << "\r";
}
return{ _max + abs(_max * zoomX), _min - abs(_min * zoomX) };
}
funcDraw::_minmaxs funcDraw::preProcessY(const double start, const double end) {
double max = INT_MIN, min = INT_MAX;
const double step = (end - start) / 100;
for (double i = start; i < end; i += step) {
double temp = functionRunnerY(i);
int dealTime = 0;
if (temp > max) max = temp;
if (temp < min) min = temp;
cout << setiosflags(ios::fixed) << setprecision(0);
cout << (i - start) / (end - start) / 2 * 100 + 50 << "%";
std::cout << "\r";
}
return{ max + abs(max * zoomY), min - abs(min * zoomY) };
}
这即是该实现的主要逻辑。完整/最新的代码可以访问github