应用EasyX图形库描绘实函数图形 木灵的炼金工作室

最近尝试了一个新的轻量级C/C++图形库EasyX,试着用它来开发一个可视化数据的项目。首先用它来试着写了一个简单绘制函数图像的小工具。

EasyX官方文档

实现逻辑比较简单,即取若干样本点,将它们两两连成线。而实际编码较为啰嗦,因为需要根据给定函数确定图像的位置、比例等参数。

核心的绘图逻辑调用了库中的putpixelline方法。

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;

其中调用了对于XY粗取样求近似最值的方法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


Copyright AmachiInori 2017-2021. All Right Reserved.
Powered By Jekyll.
amachi.com.cn