绘制统计直方图
在第二节课,用单文档程序实现鼠标画线画图后,第三节课学习了用单文档程序画统计直方图。最终效果如下。这里以统计成绩人数的为例。
1.限定直方图大小
我们先创建绘制直方图的函数,下面的代码都在函数中实现,然后在View类中调用函数就可以实现绘制直方图的功能。因为要在客户区绘制图形,所以我们需要一个CDC类型的参数,我们到时需要从文件中获取多个double类型的数据,为了方便,需要一个vector<double*类型的参数,同时有一个记录人数个数的数据number。函数是View类的成员函数。
void CScoresView::DrawScore(CDC *dc,std::vector<double>,int number)
在图形绘制前,我们先限定整个直方图的大小。先创建一个矩形对象,并用GetClientRect函数获取当前客户区的大小。
函数 | 功能 |
---|---|
GetClientRect | 获取当前客户区的大小 |
DeflateRect(int x,int y) | 朝CRect的中心移动以缩小,矩形宽度缩小2x,高度缩小2y |
CRect rc;
GetClientRect(rc);
rc.DeflateRect(40, 40);
2.画笔预设
在MFC中,CGdiObject类是GDI(图形设备接口)对象的基类,有以下派生类:CBitmap、CPalette、CRgn、CBrush、CFont和CPen,我们在此了解CBrush,CPen。
2.1 CBrush类
CBrush类的设置,可以对矩形进行填充。
函数 | 功能 |
---|---|
CBrush 标识符(画刷类型,颜色RGB) | 创建画刷 |
dc.SelectObject(画刷) | 选择画刷作当前画刷,返回旧画刷 |
颜色一般用16进制表示,如#66CCFF,转化颜色值就是(96, 192, 255)
画刷类型:
2.2 CPen类
CPen类的设置,用于绘制矩形的边缘。
函数 | 功能 |
---|---|
CPen 标识符(笔的类型,宽度,颜色RGB) | 创建画笔 |
dc.SelectObject(画笔) | 选择画笔作为当前画笔,返回旧画笔 |
画笔类型:
一些注意事项详细可参考:https://baike.baidu.com/item/CPen/7827245?fr=aladdin
内框线的特点:https://www.cnblogs.com/cgwolver/archive/2009/12/29/1635358.html
2.3 本例代码
CBrush brush1(HS_BDIAGONAL, RGB(96, 192, 255));
CBrush brush2(HS_FDIAGONAL, RGB(96, 192, 255));
CPen pen(PS_INSIDEFRAME, 2, RGB(96, 192, 255));
3.数据预处理
在绘制直方图过程中,我们需要知道区间的宽度为多少,直方图中一个人代表的矩形高度为多少,接下来我们就先处理这些数据。
int n = 5;//定义有五个区间
int width = rc.Width() / n;//一个区间的宽度
int s[] = { 0,0,0,0,0 };//区间人数置初值
for (int i = 0; i < number; i++)
{
int td = (int)db[i] / 10;
if (td < 6) s[0]++;
else if (td < 7) s[1]++;
else if (td < 8) s[2]++;
else if (td < 9) s[3]++;
else s[4]++;
}//统计各区间人数
int max_s = s[0];
for (int i = 0; i < n; i++)
if (max_s < s[i]) max_s = s[i];//找出人数最大的区间
int per_Height = rc.Height() / max_s;//算出一个人代表的矩形高度
4.绘制直方图
4.1CString类型
CString类型是MFC中最常见的类之一,用于封装字符串数据结构。字符串的连接可以用+,+=号实现,
函数 | 功能 |
---|---|
CString str0(const CString& str1) | 将str1复制到str0 |
CString str0(const CString& str1,n) | 将str1的前n个字符复制到str0 |
str0.Format(_T("%d"),i) | 将int类型的i以十进制转化为CString并赋值str0 |
int i = _ttoi(str) | 将str转化为int类型并赋值给i |
float f =_ttof(str) | 将str转化为float类型并赋值给f |
Format函数中的格式字符
格式字符 | 含义 |
---|---|
%c | 单个字符 |
%d | 十进制整型(int) |
%ld | 十进制长整型(long) |
%f | 十进制浮点数(float) |
%lf | 十进制浮点数(double) |
%o | 八进制 |
%x | 十六进制 |
%s | 字符串 |
%u | 无符号十进制数 |
_T是使用宏,自动加载当前环境的字符环境(Unicode或其他),是当前代码具有可移植性。
4.2绘制直方图相关函数
函数 | 功能 |
---|---|
CRect rect(rc) | 用矩形rc来初始化rect,rect获得rc的参数 |
rect.OffsetRect(int x,int y) | 将rect向右移动x,向下移动y(负值反向) |
dc->Rectangle(rect) | dc为cdc *类型,在客户区绘制rect矩形 |
dc->DrawText(str,rect,格式) | 在矩形位置写入格式化文本 |
DrawText中的常用格式
格式符号 | 意义 |
---|---|
DT_BOTTOM | 将正文调整到矩形底部。此值必须和DT_SINGLELINE组合。 |
DT_SINGLELINE | 显示正文的同一行 |
DT_CENTER | 使正文在矩形中水平居中 |
DT_VCENTER | 使正文在矩形中垂直居中 |
DT_LEFT | 正文左对齐 |
DT_RIGHT | 正文右对齐 |
DT_TOP | 正文顶端对齐 |
详细参考:https://www.cnblogs.com/spiritofcloud/p/3982652.html
CRect ps_rect(rc); //直方图的矩形
CRect str_rect(rc); //各区间注释矩形,用于定位
ps_rect.right = ps_rect.left + width;
CBrush* oldBrush = dc->SelectObject(&brush1);//保存旧的画刷设置
CPen* oldPen = dc->SelectObject(&pen);//保存旧的画笔设置
CString str[5] = { _T("<60"),_T("60-70"),_T("70-80"),_T("80-90"),_T(">=90") };//将每个区间的注释先存储
for(int i=0;i<n;i++)
{
ps_rect.top = ps_rect.bottom - per_Height * s[i] - 2; //该区间的高度
if (i % 2) dc->SelectObject(&brush2);
else dc->SelectObject(&brush1); //每隔一个区间用不同的斜线,左斜或右斜
dc->Rectangle(ps_rect); //绘制矩形
if (s[i] > 0)
{
CString str1;
str1.Format(_T("%d人"), s[i]);//设置str1的内容
dc->DrawText(str1, ps_rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//在矩形中间显示人数
}
str_rect = ps_rect;
str_rect.bottom = ps_rect.bottom + 20;
str_rect.top = ps_rect.bottom + 2;
dc->DrawText(str[i], str_rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
ps_rect.OffsetRect(width, 0);//移动矩形框的位置,水平方向移动width
}
dc->SelectObject(&oldBrush);
dc->SelectObject(&oldPen); //将画刷,画笔设置回原来的样子
在这里左边与平时坐标有点不同,坐标的加减是下面这个方向的。
5.读取文件数据
绘制直方图的函数已经写完,接下来在View类中的OnDraw函数中调用绘制直方图的函数便可以了。我们在调用前需要先获得数据,这里可以用回输入输出流的方法读取文件,需要包含相应的头文件
void CScoresView::OnDraw(CDC* pDC)
{
CScoresDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
//添加部分如下
std::ifstream file("D:\\score.txt", std::ios::in);
std::vector<double> datas;
double data;
while (file >> data)
datas.push_back(data);
DrawScore(pDC, datas, datas.size());
}
6.测试
文本中如下
直方图结果