那个MFC上位机做得差不多了,就剩下导出表格和做美工了。之前写了一篇链接数据库的博客,好久没更了,这次要写的是怎么绘图,虽然我这里的不是很漂亮,但是能基本满足绘图的要求,当然要修改漂亮就是再写点代码的事。
先上图吧,不过我只把绘图那部分截图出来。
这是同点异时分析,x坐标是日期,y坐标是侧磨和垂磨的值,左边是数据库查询的edit,这里选择的就是1110米初的这个点不同日期的数据分析,当然这个数据库是我自己设计的,只是测试使用。上面的侧磨和垂磨的checkbox选择就会绘,可以只选一个观察。
这幅图本来是同时异点分析的,不过数据点应该比较多,所以我没有严格限制时间就是7月17,而是时间是从7月17开始就可以的所有点。
这幅图是为了具体观察第二幅图后面那变化很小的部分,所以就用到了放大倍数,这里就定位到7月26号那天测的数据,因为数据都很小,所以要放大看,这是放大两倍的结果,效果还是不明显,所以下面这幅图就是放大8倍来看的,效果就好多了。
8倍放大就看得清楚多了,我这里x坐标的最小单位是动态变化的,从图也能看的出来,是和画的点的数量有关的。
其实这里思路很简单,按条件查询符合条件的记录,然后取出有用信息:时间、里程、百米标、十米标、侧磨、垂磨,再就是取一片区域作为绘图空间,然后就是设定坐标系空间和坐标单位和间距,最后就是把点绘出来,连线。至于其他功能,比方说侧磨和垂磨checkbox就是选择了那个就只调用那个函数绘图就好了;放大功能也很简单,原理就是改变y坐标的刻度和最小间距值,就可以达到放大效果了。
拿一个绘图函数代码来分析吧。
//侧磨绘图
//cs决定绘图的x坐标含义:时间或者位置
//x_count决定绘图的x坐标最小单位:绘图点小于30个采用固定坐标轴,大于就动态计算出最小间距
//y_mul决定绘图的y坐标最小单位:参数取值为1,2,4,8,16;说明y轴可以放大的倍数
//data[]是数据库查询结果存放的数组默认没有超过100个点,超过绘的图会很丑
void CQueryDialog::drawcemo(CString cs,int x_count,int y_mul,float data[],CString x_str[]){
int ce_x,ce_y,x_per=0;
float y_per;
CRect rectL,rect_cemo;
GetDlgItem(IDC_LIST_QUERY)->ShowWindow(false);
GetDlgItem(IDC_STATIC_QUERY)->ShowWindow(true);
GetDlgItem(IDC_STATIC_QUERY)->GetWindowRect(&rectL);//获取控件相对于屏幕的位置
ScreenToClient(rectL);//转化为对话框上的相对位置
switch(y_mul){//y坐标最小单位
case 1:y_per=10;break;
case 2:y_per=20;break;
case 4:y_per=40;break;
case 8:y_per=80;break;
case 16:y_per=160;break;
default:break;
}
//侧磨图像
rect_cemo.left = rectL.left+100;
rect_cemo.top = rectL.top+10;
//侧磨原点
ce_x = rect_cemo.left;
ce_y = rect_cemo.top+240;
//画笔
CClientDC dc(this);
CPen m_pen;
CPen *oldPen;
m_pen.CreatePen(PS_SOLID,2,RGB(0,0,0));
oldPen = dc.SelectObject(&m_pen);
//侧磨的图像坐标系
//坐标轴
dc.MoveTo(ce_x,ce_y-220);
dc.LineTo(ce_x,ce_y+30);
dc.MoveTo(ce_x,ce_y);
dc.LineTo(ce_x+850,ce_y);
//y轴箭头
dc.MoveTo(ce_x-10,ce_y-210);
dc.LineTo(ce_x,ce_y-220);
dc.LineTo(ce_x+10,ce_y-210);
//x轴箭头
dc.MoveTo(ce_x+840,ce_y-10);
dc.LineTo(ce_x+850,ce_y);
dc.LineTo(ce_x+840,ce_y+10);
//y坐标变量名
dc.TextOut(ce_x-90,ce_y-220,"侧磨/mm");
dc.TextOut(ce_x-70,ce_y,"0");
dc.MoveTo(ce_x,ce_y+20);
dc.LineTo(ce_x+5,ce_y+20);
switch(y_mul){//y坐标负值那一段的处理
case 1:dc.TextOut(ce_x-70,ce_y+15,"-2");break;
case 2:dc.TextOut(ce_x-70,ce_y+15,"-1");break;
case 4:dc.TextOut(ce_x-70,ce_y+15,"-0.5");break;
case 8:dc.TextOut(ce_x-70,ce_y+15,"-0.25");break;
case 16:dc.TextOut(ce_x-70,ce_y+15,"-0.125");break;
default:break;
}
//y坐标刻度(mm)
for(int i=1;i<11;i++){
CString str;
switch(y_mul){
case 1:str.Format("%d",2*i);break;
case 2:str.Format("%d",i);break;
case 4:str.Format("%.1f",(float)i/2);break;
case 8:str.Format("%.2f",(float)i/4);break;
case 16:str.Format("%.3f",(float)i/8);break;
default:break;
}
dc.MoveTo(ce_x,ce_y-i*20);
dc.LineTo(ce_x+5,ce_y-i*20);
dc.TextOut(ce_x-70,ce_y-i*20,str);
}
//不同分析的x坐标显示
if(strcmp(cs,"location") == 0){
dc.TextOut(ce_x+860,ce_y+10,"日期/day");
//侧磨x坐标刻度(perday)
/*if (x_count<=30)//如果绘图点少于30个就固定坐标轴,不然就是动态改变
{
x_per=20;
for(int i=1;i<=x_count;i++){
dc.MoveTo(ce_x+i*20,ce_y);
dc.LineTo(ce_x+i*20,ce_y-5);
dc.TextOut(ce_x+i*20,ce_y+10,x_str[i].Right(4));
}
}
else
{*/
x_per=830/x_count;
for(int i=1;i<=x_count;i++){
dc.MoveTo(ce_x+i*x_per,ce_y);
dc.LineTo(ce_x+i*x_per,ce_y-5);
dc.TextOut(ce_x+i*x_per,ce_y+10,x_str[i-1].Right(4));
}
//}
}else if(strcmp(cs,"time") == 0){
dc.TextOut(ce_x+860,ce_y+10,"位置/10m");
//侧磨x坐标刻度(permeter)
/*if (x_count<=30)//小于30个点就固定坐标轴,不然动态
{
x_per=20;
for(int i=1;i<=x_count;i++){
dc.MoveTo(ce_x+i*20,ce_y);
dc.LineTo(ce_x+i*20,ce_y-5);
dc.TextOut(ce_x+i*20,ce_y+10,x_str[i]);
}
}
else
{*/
x_per=830/x_count;
for(int i=1;i<=x_count;i++){
dc.MoveTo(ce_x+i*x_per,ce_y);
dc.LineTo(ce_x+i*x_per,ce_y-5);
dc.TextOut(ce_x+i*x_per,ce_y+10,x_str[i-1]);
}
//}
}
//侧磨
dc.MoveTo(ce_x+x_per,ce_y-(int)(data[0]*y_per));
for(int i=2;i<=x_count;i++){
dc.LineTo(ce_x+i*x_per,ce_y-(int)(data[i-1]*y_per));
}
m_pen.DeleteObject();
dc.SelectObject(oldPen);
}
这个函数的几个参数是我自己定义的,为了方便实现我要的功能,前面我都注释了这几个的含义,这里就不多说了。我依次往下说这个函数的功能吧。首先是获取绘图的区域,也就是图片中那个矩形,然后获取左上角那个点的相对屏幕位置,这是为了定一个原点,这个原点决定以后我们绘图的位置。然后是一个switch,这个注释说是y坐标的最小单位,其实作用就是放大,默认是选择1,就是保持原来的大小,然后如果数据很小,就要放大,这时这个y_per的含义就是要乘以到数据上的相对值,这个值就是对数据进行放大,或者这么理解,就是y坐标的每个mm的相对值的多少,放大就是要把每mm的相对值增加。
ce_x和ce_y是一个基准值,也是我设定的显示出来的坐标系的真实原点坐标。然后是画笔Cpen这个很简单,我只用了黑色一种,所以不好看,可以自定义,不多说。然后是绘坐标系,这个很简单,就是一条直线一个拐角,用的就是MoveTo和LineTo这两个函数就可以了,一个是定点,一个是移动到另外一个点,很好理解。重点来了,就是刻度的标注,先说y坐标的。这个毫无疑问都是mm单位,但是每个刻度的标注是不同的,因为有放大倍数,1倍的时候就是原数据,2倍就是原坐标位置数据除以2,依次类推,很好理解。x坐标每个刻度标注也不同,因为每次查询出来的点的数量都不一样,这里就是按照点的数量,也就是x_count来确定每个点大概间距,也就是相对值是多少,然后确定x_per,然后画出每个刻度值,不过这里还有一点要注意,就是x刻度是变化的,分为日期和位置两类,也是根据函数传的参数决定的,然后就是日期的话就直接读取数据库里面日期,然后字符串截断取后四个字符就行;如果是位置的话,就读数据库里面的里程、百米标、十米标,然后字符串合并就可以了。
最后就是绘曲线了,也是那两个函数,从第一个点开始然后一直连线到最后一个点就可以绘出图形了。
再把绘图的按键响应函数的代码给出来吧。
//同时异点
void CQueryDialog::OnBnClickedTime()
{
// TODO: 在此添加控件通知处理程序代码
//查询的值绘图
float float_sidegrinding[100];
float float_verticalmill[100];
CString cstr_location[100];
int count = 0;
CString cstr_mul="1";
CString sql = SortDB();
CString tempsql = "";
tempsql = G2U(sql);
try{
CppSQLite3DB db;
db.open(m_exclefilepath);
// 判断是否存在表data、建立表
if (db.tableExists("data"))
{
// 查询
CppSQLite3Query q;
if(sql==""){
AfxMessageBox("没有选择日期信息,请填写开始时间和结束时间");
}else{
CppSQLite3Buffer bufSQL;
bufSQL.format("select * from data where %s;",tempsql);
q = db.execQuery(bufSQL);
}
while (!q.eof())
{
CString str_mileage = CString(q.fieldValue(5));
CString str_hundred = CString(q.fieldValue(6));
CString str_ten = CString(q.fieldValue(7));
int location = atoi(U2G(str_mileage))*100+atoi(U2G(str_hundred))*10+atoi(U2G(str_ten));
cstr_location[count].Format("%d",location);
CString str_sidegrinding = CString(q.fieldValue(10));
CString str_verticalmill = CString(q.fieldValue(11));
float_sidegrinding[count] = atof(U2G(str_sidegrinding));
float_verticalmill[count] = atof(U2G(str_verticalmill));
count ++;
if (count>=100) break;//超过100个点就只绘100个点
q.nextRow();
}
q.finalize();
}
else
{
AfxMessageBox("表data不存在");
}
}
catch (CppSQLite3Exception& e)
{
// 出错
}
if (count!=0)
{
GetDlgItemText(IDC_COM_MUL,cstr_mul);
if ( BST_CHECKED == IsDlgButtonChecked(IDC_CHECKCEMO) )
{
// 勾选
drawcemo("time",count,atoi(cstr_mul),float_sidegrinding,cstr_location);
}
if ( BST_CHECKED == IsDlgButtonChecked(IDC_CHECKCHUIMO) )
{
// 勾选
drawchuimo("time",count,atoi(cstr_mul),float_verticalmill,cstr_location);
}
}
}
这里就是数据库查询,然后把查询的结果要的部分保存起来,然后有一些是字符串的操作,合并和拆分,哈有就是checkbox的选择和函数的参数的设置,可以看看。