个人作品

合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下

基于 EasyX 的实时信号绘制工具(by Ephemeroptera)

由于最近从 matlab 转接到 vs 平台继续自己的仿真工作,但是却一直没有找到简易 plot 工具,于是尝试用 EasyX 开发一个绘制波形的小工具,希望可以帮助到有需要的人。

执行效果如图:

完整源代码如下:

/******************************************************************************************/
// EasyPlot 是基于 EasyX 绘图库(https://www.easyx.cn)而编写的,对实时信号进行绘制的一个工具
// 以下代码为了便于学习以源文件形式展示,如果应用于工程建议采用头文件形式封装
// 本文采用Unicode字符集,ANSI环境的读者注意相关地方格式的转换
// 作者:Ephemeroptera
// 地点:安徽大学
// 联系方式:605686962@qq.com
// 最后修改时间:2018-11-29
/*******************************************************************************************/
#include <iostream>
#include <easyx.h>
#include <conio.h>
#include <sstream>
#include <string>
#define PI 3.141592

/*************** 定义动态 X 坐标结构体 ************************/
struct AxisX {
	double index; // 坐标
	double label; // 标签
};
/*************** 定义数字转 LPWSTR 字符型函数 ****************/
void num2WSTR(double x, wchar_t* &buffer);

/*************** 定义类:动态窗口 ****************************************************************************/
// 说明:动态窗口实现绘制实时信号的原理,其实是将当前信号送入定长队列 DynaQue,
//      并在每一帧进行绘制,其中队列长度由分辨率决定。最大分辨率应该 <= COLS
//      例如:DynaQue[xResolution+1] = {DynaQue[0],DynaQue[1,..,DynaQue[xRelolution]},其中 DynaQue[N] 为最近信号
//            那么相邻点距离为:COLS/xResolution
// 当然 X 坐标也需要动态更新,同理类中也定义了队列 xAxis 对坐标进行同步更新
//      例如:xAxis[xSegment+1] = {xAxis[0],xAxis[1],..,xAxis[xSegment]},其中 xAxis[N] 保存的是 AxisX 数据
//             那么相邻刻度距离:COLS/xSegment
/***************************************************************************************************************/
class DynaWin {
public:
	DynaWin(double XLIM, double YLIM, unsigned short xSegment, unsigned short ySegment, int xResolution);	// 构造器
	void UpateWin(double y);	// 更新窗口
	void ShowWin();				// 绘制窗口
	void InitBackGround();		// 背景初始化(静态窗口)

private:
	const int CENTERX = 50;		// 定义原点
	const int CENTERY = 200;
	const int ROWS = 150;		// 定义边长
	const int COLS = 1000;

	double XLIM;				// 定义 X 轴区间
	double YLIM;				// 定义 Y 轴区间
	unsigned short xSegment;	// 定义坐标的刻度
	unsigned short ySegment;
	int xResolution;			// x轴分辨率(决定 Dynaque 的长度)
	int DynaNum = 0;			// 已读信号个数
	double* DynaQue;			// 实时信号队列
	AxisX* xAxis;				// 实时坐标队列
	IMAGE* canvas;				// 当前画布
	IMAGE local;				// 局部背景

	void UpdateAxisX();			// 更新坐标

};
/*************************** MAIN 函数 *********************************/
int main()
{
	// 定义动态窗口:
	//            X 坐标:长度 = 2 * PI,	10 个刻度,500 分辨率(采样点)
	//            Y 坐标:长度 = 1,		5 个刻度
	DynaWin win(2 * PI, 1, 10, 5, 500);
	win.InitBackGround(); //背景初始化
	int i = 0;

	// 批量绘制:绘制一个y=sin(wt),其中 w = 2*π*f , t = n*T0 =n/Fs,这里我们绘制 f = 5 的正弦信号
	BeginBatchDraw();
	while (1)
	{
		win.UpateWin(sin(2 * PI * 5 * i / 500));	// 更新窗
		win.ShowWin();								// 显示窗
		FlushBatchDraw();
		Sleep(10);
		++i;
	}

	EndBatchDraw();
	// 按任意键退出
	_getch();
	closegraph();
	return 0;
}

/******************* 以下是相关方法的具体实现 **************************/
// 数字转 LPWSTR 字符
void num2WSTR(double x, wchar_t* &buffer)
{
	// NUM TO WSS TO WSTR
	std::wstringstream ss;
	ss << x;
	size_t size = ss.str().length();
	buffer = new wchar_t[size + 1];
	ss >> buffer;
	buffer[size] = 0;
}

// DynaWin 构造函数
DynaWin::DynaWin(double XLIM, double YLIM, unsigned short xSegment, unsigned short ySegment, int xResolution)
{
	this->XLIM = XLIM;
	this->YLIM = YLIM;
	this->xSegment = xSegment;
	this->ySegment = ySegment;
	this->xResolution = xResolution;
	this->DynaQue = new double[xResolution + 1];
	this->xAxis = new AxisX[xSegment + 1];
}

//背景初始化
void DynaWin::InitBackGround()
{
	// 定义画布
	initgraph(1100, 400, SHOWCONSOLE);
	// 初始化画笔
	setlinecolor(RED);
	setlinestyle(PS_SOLID | PS_ENDCAP_ROUND | PS_JOIN_ROUND, 3);
	// 定义坐标原点以及方向
	setorigin(CENTERX, CENTERY);
	setaspectratio(1, -1);
	// 绘制静态窗口
	rectangle(0, 150, 1000, -150);
	line(0, 0, 1000, 0);
	// 标注说明
	setbkmode(TRANSPARENT);
	settextcolor(YELLOW);
	setaspectratio(1, 1);// 纠正字体倒置
	settextstyle(30, 0, L"宋体");
	outtextxy(450, -185, L"EasyPlot");
	settextstyle(10, 0, L"宋体");
	outtextxy(850, 160, L"Author:Ephemeroptera");
	outtextxy(870, 180, L"Version:1.2.0");
	// 绘制刻度
	unsigned short yVertex = 2 * ySegment + 1;
	double yUnit = YLIM / ySegment;
	setbkmode(TRANSPARENT);
	settextcolor(YELLOW);
	settextstyle(15, 0, L"10");
	setlinestyle(PS_DASH, 1);				// 虚线
	// 水平刻度线
	for (int i = 1; i <= yVertex; ++i)
	{
		// 表明 y 刻度值
		double label = YLIM - (i - 1)*yUnit;
		wchar_t* buffer;
		num2WSTR(label, buffer);
		outtextxy(-30, (int)((i - ySegment - 1)*(ROWS / ySegment)) - 10, buffer);
		delete[] buffer;
		buffer = (wchar_t*)NULL;

		// 画出虚线
		line(0, (int)((i - ySegment - 1)*(ROWS / ySegment)), 1000, (int)((i - ySegment - 1)*(ROWS / ySegment)));
	}
	setaspectratio(1, -1);					// 恢复坐标方向
	canvas = GetWorkingImage();				// 画板地址
	getimage(&local, -50, -160, 1100, 320);	// 保存局部背景
}


// 更新窗
void DynaWin::UpateWin(double y)	// 更新 win
{
	++DynaNum;										// 信息个数
	UpdateAxisX();									// 更新AxisX

	if (DynaNum <= xResolution + 1)					// 队列未满
		DynaQue[DynaNum - 1] = y;

	else	// 队列已满
	{
		for (int i = 0; i <= xResolution - 1; ++i)	// 出队列
		{
			DynaQue[i] = DynaQue[i + 1];			// 向左移位
		}
		DynaQue[xResolution] = y;					// 尾部添加新元素
	}
}

// 绘制窗
void DynaWin::ShowWin()
{
	// 选定画板
	SetWorkingImage(canvas);

	// 背景覆盖	
	putimage(-50, -160, &local);

	// (1) 显示 AxisX
	setlinecolor(RED);
	setlinestyle(PS_DASH, 2);
	for (int j = 0; j <= xSegment - 1; ++j)
	{
		// 绘制虚线
		line((int)xAxis[j].index, -150, (int)xAxis[j].index, 150);
		// 绘制标注
		setaspectratio(1, 1);
		setbkmode(TRANSPARENT);
		settextcolor(YELLOW);
		settextstyle(15, 0, L"10");
		double label = xAxis[j].label;
		wchar_t* wstr;
		num2WSTR(label, wstr);
		outtextxy((int)xAxis[j].index - 10, 0, wstr);
		delete[] wstr;
		wstr = NULL;
		setaspectratio(1, -1);						// 回复坐标系
	}

	// (2) 显示 DynaQue
	setlinecolor(WHITE);
	setlinestyle(PS_SOLID | PS_ENDCAP_ROUND | PS_JOIN_ROUND, 2);
	if (DynaNum <= xResolution)						// 队列未满
	{
		for (int i = 0; i <= DynaNum - 2; ++i)		// 0 to DynaNum-1 (DynaNum 个)
		{
			line((int)(i*COLS / xResolution), (int)(DynaQue[i] / YLIM * ROWS), (int)((i + 1)*COLS / xResolution), (int)(DynaQue[i + 1] / YLIM * ROWS));
		}
	}
	else	// 队列已满
	{
		for (int i = 0; i <= xResolution - 1; ++i)	// 0 to xResolution (xResolution + 1 个) 即显示全部 DynaQue
		{
			line((int)(i * COLS / xResolution), (int)(DynaQue[i] / YLIM * ROWS), (int)((i + 1)*COLS / xResolution), (int)(DynaQue[i + 1] / YLIM * ROWS));
		}
	}
}

// 更新坐标
void DynaWin::UpdateAxisX()
{
	if (DynaNum <= xResolution)										// 队列未满
		for (int i = 0; i <= xSegment - 1; ++i)
		{
			xAxis[i].index = (i + 1)*COLS / xSegment;
			xAxis[i].label = (i + 1)*XLIM / xSegment;
		}
	else	// 队列已满
	{

		for (int i = 0; i <= xSegment - 1; ++i)						// 向左移动
		{
			xAxis[i].index -= COLS / xResolution;
		}
		if (xAxis[0].index <= 0)									// AxisX 动态更新
		{
			int j;
			for (j = 0; j <= xSegment - 2; ++j)						// 队列更替
			{
				xAxis[j].index = xAxis[j + 1].index;
				xAxis[j].label = xAxis[j + 1].label;
			}
			xAxis[j].index = xAxis[j - 1].index + COLS / xSegment;	// 尾部添加新元素
			xAxis[j].label = xAxis[j - 1].label + XLIM / xSegment;
		}
	}
}
作者:Ephemeroptera
地点:安徽大学
邮箱:605686962@qq.com

添加评论