1 Star 0 Fork 45

SitOnLotus / mfcplot

forked from rewine / mfcplot 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

mfcplot

mfc数学函数曲线绘制程序

实验环境

  • window 10 版本19041
  • Microsoft Visual Studio Community 2019 版本 16.6.4

实现功能

  • 绘制常见一元函数的图像
  • 支持普通函数,极坐标函数,参数方程,直接输入数据点
  • 可以删除指定函数图像
  • 可以在一个坐标系中绘制多条数学曲线
  • 显示坐标轴,网格,刻度值,图例
  • 可以选择不同颜色线型来绘制不同的曲线
  • 当鼠标移动到曲线上某点时,可以显示该点的坐标
  • 可以用鼠标拖动图像
  • 可以进行图形的放大,缩小,定量设置显示范围,自动缩放
  • 普通函数x取值范围可设置为跟随显示范围变化
  • 状态栏实时显示鼠标位置,双击显示鼠标精确位置
  • 重要数据的序列化和反序列化
  • 突变函数(如$floor(x)$)和部分y值接近无穷的函数(如$tan(x)$)无法完美显示

界面展示

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

在这里插入图片描述 在这里插入图片描述

在这里插入图片描述

设计结构

程序流程及设计

  用户输入函数信息,显示函数图像。还可以通过菜单和工具栏更改设置。

  通过计算样本点,相邻样本点用直线连接,当样本点数量足够大时,可近似看成曲线。 在这里插入图片描述

在这里插入图片描述

程序所有源文件

在这里插入图片描述

CalculatorFunc.cpp介绍

double CalcEquation(CString m_sEquation, bool& succ, char xKey, double xVal) m_sEquation:表达式 succ:计算是否成功 xKey:未知数是"x"还是"t" xVal:未知数的值

此文件可以单独拿出来(把CString换成string就行)使用 bool succ = true; double ans = CalcEquation("sin(x)+e^x",succ,'x',1.1);

 对方程,分为数(常数,未知数x),双目运算符(+ , - ,* , / , ^ ) ,单目运算符(sin,cos等等),单目运算符后面是一个完整的子式,如2+cos(x+1)中,x+1就是一个完整的式子,通过递归调用可以求子式的值,如果已知子式值,这个[单目运算符+子式]就是一个已知数了,那剩下的就等效于只有+ , - , * , / , ^ 的公式,用表达式栈法就可以解决。

FuncData.cpp介绍

在这里插入图片描述

FuncData
成员变量:
	int FuncCas;            //函数类型
	CString m_Equation;		//函数表达式
	double minX, maxX;		//x极值
	double maxY, minY;		//y极值
	int stepX;				//可以理解为样本点的数量
	int m_penWidth;			//画笔宽度
	int m_penType;			//画笔类型
	COLORREF m_color;		//画笔颜色
	vector<pair<double, double> > vetPoint;			 //储存所有样本点
成员函数:
	virtual double GetY(double xVal, bool& succ) = 0;//得到未知数为xVal时函数值,succ表示计算是否成功
	virtual bool CalcList() = 0;					 //计算vetPoint
	virtual bool GetNearest(pair<double,double> NowPoint, pair<double, double> &CmpPoint);//获取本函数与NowPoint最近的点
	virtual CString GetEquation2();					 //为了得到参数方程第二个函数式
	FuncData();
	FuncData(CString Equation,double minX,double maxX,int stepX,COLORREF color, int penWidth,int penType);//构造函数

注意:此处派生类只记录特有的成员
NormalFD


PolarFD 
成员变量
double maxth, minth;   //自变量θ取值范围

TwoFD
成员变量
CString m_EquationY;
double maxT, minT;
成员函数
double GetX(double tVal, bool& succ);//参数方程X也需要求值
	virtual double GetY(double tVal, bool& succ);

DataFD
成员函数
	static int DataFD_Cnt;  //记录数据点类型函数数量
mfcplotDoc.cpp介绍

 mfcplotDoc中记录着设置信息和函数数据,主要内容如下

public:
	bool m_WillShowGrid; //是否显示网格
	bool m_WillShowAxis; //是否显示坐标轴
	bool m_WillShowEdge; //是否显示边框
	bool m_SingelMode;   //单函数模式添加函数自动删除上一个函数
	bool m_ForceXrange;	 //普通函数x范围是否固定,不固定的话随显示范围变化
	bool m_ShowNearPoint;//鼠标接近函数线时是否显示其坐标
	double m_Xmin, m_Xmax, m_Ymin, m_Ymax;//显示范围
	FuncData *m_FD;//临时变量
	CObList m_List;//记录所有函数信息
public:
	afx_msg void OnAxisMenu();//坐标轴
	afx_msg void OnGridMenu();//网格
	afx_msg void OnEdgeMenu();//边框
	afx_msg void OnSmallerMenu();//显示范围缩小 0.8
	afx_msg void OnBiggerMenu();//显示范围放大 1.25
	afx_msg void OnNormalFuncMenu();//增加普通函数
	afx_msg void OnMenuSetXyrange();//设置显示范围
	afx_msg void OnFuncMode();//单/多函数模式
	afx_msg void OnPolarFuncMenu();//增加极坐标函数
	afx_msg void OnTwoFuncMenu();//增加参数方程函数
	afx_msg void OnDataFuncMenu();//增加数据点型函数
	afx_msg void OnFroceXrang();//普通函数x范围是否固定
	afx_msg void OnNearpointMenu();//是否显示最近点
	afx_msg void OnAutorangeMenu();//自动调整显示范围,正好显示完整函数图像
	afx_msg void OnDelfunconeMenu();//删除一个函数
	afx_msg void OnDelallMenu();//删除所有函数
mfcplotView.cpp介绍

 绘图逻辑是在这里实现的,主要内容如下

public:
	double m_Xmin, m_Xmax, m_Ymin, m_Ymax;//函数显示范围
	int nTop, nButton, nLeft, nRight;//对应的逻辑坐标范围
	int isMoving;//拖动状态 0不拖动 1拖动模式 2正在拖动
	double tmp_Xmin, tmp_Xmax, tmp_Ymin, tmp_Ymax;
	//拖动模式下,单击鼠标左键,记录起点的显示范围
	CPoint m_posStart;
	//拖动模式下,单击鼠标左键,记录起点的鼠标坐标
	//根据鼠标坐标偏移量可以计算显示范围变化量

 函数中LPxtoFPx表示把函数坐标x变成pDC可以用的逻辑坐标_x,原理函数坐标范围m_Xmin,m_Xmax到逻辑坐标范围nLeft,nRight(下面函数会给出)等比例的映射。

double CmfcplotView::LPxtoFPx(int x) {
	return m_Xmin + (1.0 * x - nLeft) * (m_Xmax - m_Xmin) / (1.0 * nRight - nLeft);
}

 实现坐标转换后就可以进行绘图工作了。

void CmfcplotView::OnDraw(CDC* pDC)
{
	CmfcplotDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;
	m_Xmin = pDoc->m_Xmin;//极值保存在Doc中
	m_Xmax = pDoc->m_Xmax;
	m_Ymin = pDoc->m_Ymin;
	m_Ymax = pDoc->m_Ymax;
	
	CRect rect;
	GetClientRect(&rect);//获得视图区

	nTop = (int)round(rect.bottom * 0.1);  //函数图像不会占据整个视图区
	nButton = (int)round(rect.bottom * 0.9);
	nLeft = (int)round(rect.right * 0.1);
	nRight = (int)round(rect.right * 0.9);

	if (pDoc->m_WillShowEdge) {//画边框
		pDC->MoveTo(nLeft, nTop);
		pDC->LineTo(nLeft, nButton);
		pDC->LineTo(nRight, nButton);
		pDC->LineTo(nRight, nTop);
		pDC->LineTo(nLeft, nTop);
	}

	//画x坐标信息
	int nX,nY;
	bool BIGX = abs(m_Xmin) > 100 || abs(m_Xmax) > 100;//x坐标值比较大时,标注更稀
	for (nX = nLeft; nX < nRight; nX += (BIGX ? 100 : 50)) { //每隔100/50像素一个标注
		CRect textRect(nX - (BIGX ? 50 : 25), nButton + 1, nX + (BIGX ? 50 : 25), nButton + 20);//显示区域
		CString xInfo;
		xInfo.Format(_T("%.2f"),LPxtoFPx(nX));
		pDC->DrawText(xInfo, &textRect, DT_SINGLELINE | DT_CENTER);
		                                    //单行,上下左右居中显示
	}
	if (nX - nRight <= (BIGX ? 50 : 25)) {//最后一个x坐标,与前一个标注距离太近则不显示
		CRect textRect(nRight, nButton + 1, nRight + 50, nButton + 20);
		CString xInfo;
		xInfo.Format(_T("%.2f"),m_Xmax);
		pDC->DrawText(xInfo, &textRect, DT_SINGLELINE | DT_LEFT | DT_TOP);
	}

	//y坐标
	for (nY = nButton - 50; nY > nTop; nY -= 50) {
		CRect textRect(nLeft - 200, nY-10, nLeft - 3, nY + 10);
		CString yInfo;
		yInfo.Format(_T("%.2f"), LPytoFPy(nY));
		pDC->DrawText(yInfo, &textRect, DT_SINGLELINE | DT_RIGHT);
	}
	if (nTop - nY <= 25) {
		CRect textRect(nLeft - 200, nTop - 10, nLeft - 3, nTop + 10);
		CString yInfo;
		yInfo.Format(_T("%.2f"),m_Ymax);
		pDC->DrawText(yInfo, &textRect, DT_SINGLELINE | DT_BOTTOM | DT_RIGHT);
	}


	//  显示网格
	if (pDoc->m_WillShowGrid) {
		CPen pen(PS_DOT, 1, RGB(100, 100, 100));           //创建笔,虚线,并调整坐标颜色灰色
		CPen *pOldPen = (CPen *)pDC->SelectObject(&pen);
		for (nX = nLeft+50; nX < nRight; nX += 50) {
			pDC->MoveTo(nX, nTop);
			pDC->LineTo(nX, nButton);
		}
		for (nY = nButton - 50; nY > nTop; nY -= 50) {
			pDC->MoveTo(nLeft, nY);
			pDC->LineTo(nRight, nY);
		}
		pDC->SelectObject(pOldPen);
	}

	// 显示坐标轴
	if (pDoc->m_WillShowAxis) {
		CPen pen(PS_SOLID, 2, RGB(0, 0, 0));
		CPen* pOldPen = (CPen*)pDC->SelectObject(&pen);
		int oX = FPxtoLPx(0);
		int oY = FPytoLPy(0);
		bool showY = oX >= nLeft && oX <= nRight;
		bool showX = oY >= nTop && oY <= nButton;//判断x,y轴是否在范围内
		if (showX) {
			pDC->MoveTo(nLeft - 10, oY);
			pDC->LineTo(nRight + 10, oY);
		}
		if (showY) {
			pDC->MoveTo(oX, nButton + 10);
			pDC->LineTo(oX, nTop - 10);
		}
		if (showX && showY) {
			pDC->TextOutW(oX + 1, oY + 1, _T("O"));
		}
		if (showX) {
			pDC->MoveTo(nRight + 10, oY);
			pDC->LineTo(nRight + 5, oY + 5);
			pDC->MoveTo(nRight + 10, oY);
			pDC->LineTo(nRight + 5, oY - 5);
			pDC->TextOutW(nRight + 10, oY, _T("X轴"));
		}
		if (showY) {
			pDC->MoveTo(oX, nTop - 10);
			pDC->LineTo(oX - 5, nTop - 5);
			pDC->MoveTo(oX, nTop - 10);
			pDC->LineTo(oX + 5, nTop - 5);
			pDC->TextOutW(oX + 5, nTop - 10, _T("Y轴"));
		}
		pDC->SelectObject(pOldPen);
	}

	
	POSITION p = pDoc->m_List.GetHeadPosition();

	int showTop = nTop;
	while (p != nullptr) {
		bool shouldMov = true;//一段曲线第一个点MoveTo,其他都是LineTo
		FuncData* tmpFD = (FuncData*)pDoc->m_List.GetNext(p);
		CPen pen(tmpFD->m_penType, tmpFD->m_penWidth, tmpFD->m_color);
		CPen* pOldPen = (CPen*)pDC->SelectObject(&pen);

		if (tmpFD->FuncCas == CAS_NORMAL) {//动态X坐标模式下,普通函数x范围与视图不同时自动同步
			if (pDoc->m_ForceXrange && isMoving!=2)
				if (tmpFD->minX != m_Xmin || tmpFD->maxX != m_Xmax) {
					tmpFD->minX = m_Xmin;
					tmpFD->maxX = m_Xmax;
					tmpFD->CalcList();
				}
		}

		for (auto dot : tmpFD->vetPoint) {
			if (dot.first < m_Xmin || dot.first > m_Xmax || dot.second < m_Ymin || dot.second > m_Ymax || dot.second != dot.second) {
				shouldMov = true;
				continue;
			}
			if (shouldMov) {
				pDC->MoveTo(FPxtoLPx(dot.first), FPytoLPy(dot.second));
				shouldMov = false;
			}
			else
				pDC->LineTo(FPxtoLPx(dot.first), FPytoLPy(dot.second));
		}

		pDC->MoveTo(nRight+5, showTop);//显示图例
		pDC->LineTo(rect.right, showTop);
		showTop += 5;
		if (tmpFD->FuncCas == CAS_NORMAL)
		    pDC->TextOutW(nRight + 5, showTop, _T("f(x)=")+tmpFD->m_Equation);
		else if (tmpFD->FuncCas == CAS_POLAR)
			pDC->TextOutW(nRight + 5, showTop, _T("r(t)=") + tmpFD->m_Equation);
		else if (tmpFD->FuncCas == CAS_TWO) {
			pDC->TextOutW(nRight + 5, showTop, _T("x(t)=") + tmpFD->m_Equation);
			showTop += 20;
			pDC->TextOutW(nRight + 5, showTop, _T("y(t)=") + tmpFD->GetEquation2());
		} else if (tmpFD->FuncCas == CAS_DATA)
			pDC->TextOutW(nRight + 5, showTop, _T("y(t)=") + tmpFD->m_Equation);
		showTop += 25;
		pDC->SelectObject(pOldPen);
	}
}

 值得一提的是OnMouseMove的代码,拖动模式下,起点信息已经在OnLButtonDown更新,拖动过程使用了双缓冲绘图防止闪烁。具体原理可参看文末参考资料。

void CmfcplotView::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	更新状态栏,此处省略
	if (isMoving==2) {
		::SetCursor(LoadCursor(NULL, IDC_SIZEALL));
		CmfcplotDoc* pDoc = GetDocument();
		double detx = LPxtoFPx(point.x) - LPxtoFPx(m_posStart.x);
		pDoc->m_Xmin = tmp_Xmin - detx;
		pDoc->m_Xmax = tmp_Xmax - detx;
		double dety = LPytoFPy(point.y) - LPytoFPy(m_posStart.y);
		pDoc->m_Ymin = tmp_Ymin - dety;
		pDoc->m_Ymax = tmp_Ymax - dety;
		CDC* pDC = GetDC();
		//创建一个内存中的显示设备
		CDC MemDC;
		MemDC.CreateCompatibleDC(NULL);
		//创建一个内存中的图像
		CBitmap MemBitmap;
		CRect rect;
		GetClientRect(&rect);
		MemBitmap.CreateCompatibleBitmap(pDC, rect.right, rect.bottom);
		//指定内存显示设备在内存中的图像上画图
		MemDC.SelectObject(&MemBitmap);
		//先用一种颜色作为内存显示设备的背景色
		MemDC.FillSolidRect(rect.left, rect.top, rect.right, rect.bottom, RGB(144, 144, 144));
		this->OnDraw(&MemDC);
		//将内存中画好的图像直接拷贝到屏幕指定区域上
		pDC->BitBlt(rect.left, rect.top, rect.right, rect.bottom, &MemDC, 0, 0, SRCCOPY);
		//释放相关资源
		ReleaseDC(pDC);
	}
	else if (isMoving == 1) {
		::SetCursor(LoadCursor(NULL, IDC_HAND));
	}
	显示函数最近点部分,此处省略
	CView::OnMouseMove(nFlags, point);
}

完整代码

github gitee csdn下载

参考资料

MIT License Copyright (c) 2020 wineee Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

mfc数学函数曲线绘制程序 展开 收起
C++
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
C++
1
https://gitee.com/SitOnLotus/mfcplot.git
git@gitee.com:SitOnLotus/mfcplot.git
SitOnLotus
mfcplot
mfcplot
master

搜索帮助