对于一些内部系统的项目,各种图表是在所难免的,因为图表可以更加清晰的表达出想看到的数据。

因为之前从来没有做过关于图表的东西,唯一能想到的就是“验证码”,所以应该是一个思路,用GDI去搞。

数据懒着去搞了,记得前些日子在亚航官网查机票,就想到这些数据还挺适合做这个DEMO的,所以就先借用一下亚航的数据喽。

数据大概就是这样子的,日期及价钱。

我选了其中“9月27日-10月10日”正好两周的数据作为此次Demo的测试数据。

原理就是跟实现验证码一模一样,通过给<img>标签修改src属性来操作柱形图的变化,src属性导向的页面是另外一个页,非显示柱形图本页。

然后就是如何利用GDI画柱形图。

先上一下效果图:


下面看一下部分代码片段

html代码:

<body><formid="form1"runat="server"><div><asp:ImageButtonID="img_last"runat="server"ImageUrl="~/Left.jpg"AlternateText="Left"OnClientClick="returnfalse;"/><imgid="img_bar"src="GenerateImage.aspx?page_num=0&from=北京&to=清迈"alt=""/><asp:ImageButtonID="img_next"runat="server"ImageUrl="~/Right.jpg"AlternateText="Right"OnClientClick="returnfalse;"/></div><asp:HiddenFieldID="hf_pagenum"runat="server"Value="0"/><asp:HiddenFieldID="hf_recordnum"runat="server"/></form></body>

js代码:

$(function(){var$pagenum=$("#hf_pagenum");var$recordnum=$("#hf_recordnum");$("#img_last").click(function(){if(parseInt($pagenum.val())-1>=0){$pagenum.val(parseInt($pagenum.val())-1);$("#img_bar").attr("src","GenerateImage.aspx?page_num="+$pagenum.val()+"&from=北京&to=清迈");}})$("#img_next").click(function(){if(parseInt($pagenum.val())+1<parseInt($recordnum.val())/7){$pagenum.val(parseInt($pagenum.val())+1);$("#img_bar").attr("src","GenerateImage.aspx?page_num="+$pagenum.val()+"&from=北京&to=清迈");}})})

后台代码:

1、一些坐标点的声明

privatereadonlyintWEEKDAYS=7;//一周的天数privatereadonlyintY_UNIT_NUM=10;privatereadonlyintCHAR_X_LEFT=110;privatereadonlyintCHAR_X_TOP=388;privatereadonlyintCHAR_Y_LEFT=30;privatereadonlyintCHAR_Y_TOP=79;privatereadonlyintTITLE_LEFT=140;//柱形图标题起始X坐标privatereadonlyintTITLE_TOP=18;//柱形图标题起始Y坐标privatereadonlyintTIME_LEFT=170;//柱形图日期起始X坐标privatereadonlyintTIME_TOP=48;//柱形图日期起始Y坐标privatereadonlyintGRID_X_LEFT=150;//背景网格X左边位移privatereadonlyintGRID_X_TOP=80;//背景网格X上边位移privatereadonlyintGRID_X_BOTTOM=380;//背景网格X下边位移privatereadonlyintGRID_Y_TOP=110;privatereadonlyintGRID_Y_LEFT=100;privatereadonlyintGRID_Y_RIGHT=580;privatereadonlyintGRID_UNIT_WIDTH=50;//网格单元宽度privatereadonlyintGRID_UNIT_HEIGHT=30;//网格单元高度privatereadonlyintDATA_UNIT_WIDTH=40;//柱单元宽度privatereadonlyintDATA_UNIT_HEIGHT=30;//柱单元高度

2、筛选当页数据方法

为了是通过点击下一页,上一页按钮,对数据进行筛选,选出当页显示数据进行绘制

protectedDataSetQueryDisplayRecord(intpagenum){DataSetds=newDataSet();DataTabledt=newDataTable();dt.Columns.Add(newDataColumn("date"));dt.Columns.Add(newDataColumn("price"));for(inti=7*pagenum;i<(goStr.Count>=WEEKDAYS*(pagenum+1)?WEEKDAYS*(pagenum+1):goStr.Count);i++){DataRowdr=dt.NewRow();dr["date"]=goStr.Keys.Take(i+1).Last();;dr["price"]=goStr.Values.Take(i+1).Last();;dt.Rows.Add(dr);}ds.Tables.Add(dt);returnds;}

哦,对了,这里用的是dictionary<string, string>暂时存的模拟数据,正常情况当然要是通过数据库了。

3、生成图片

protectedvoidCreateImage(stringfrom,stringto,intpagenum,DataSetds){Fontfont_Note=newSystem.Drawing.Font("Arial",9,FontStyle.Regular);//x轴,y轴,解释内容字体小字Fontfont_GraphicName=newSystem.Drawing.Font("微软雅黑",18,FontStyle.Regular);//图表名称字体大字Fontfont_GraphicTime=newSystem.Drawing.Font("微软雅黑",14,FontStyle.Regular);//图表时间字体大字Brushbrush_Blue=newSolidBrush(Color.Black);//蓝色线条if(ds!=null){intheight=620,width=650;System.Drawing.Bitmapp_w_picpath=newSystem.Drawing.Bitmap(width,height);Graphicsg=Graphics.FromImage(p_w_picpath);g.Clear(Color.White);System.Drawing.Drawing2D.LinearGradientBrushbrush=newSystem.Drawing.Drawing2D.LinearGradientBrush(newRectangle(0,0,p_w_picpath.Width,p_w_picpath.Height),Color.LightGray,Color.LightGray,1.2f,true);//调用FillRectangle方法使用指定颜色填充柱形图的内部g.FillRectangle(Brushes.WhiteSmoke,0,0,width,height);Brushbrush2=newSolidBrush(Color.Black);//应用DrawString方法输出文字信息g.DrawString(from+"至"+to+"机票分析图(单位:CNY)",font_GraphicName,brush_Blue,newPointF(TITLE_LEFT,TITLE_TOP));//画图片的边框线g.DrawRectangle(newPen(Color.Black),0,0,p_w_picpath.Width-1,p_w_picpath.Height-1);Penmypen=newPen(brush,1);//绘制横向线条for(inti=0;i<WEEKDAYS;i++){g.DrawLine(mypen,GRID_X_LEFT+GRID_UNIT_WIDTH*i,GRID_X_TOP,GRID_X_LEFT+GRID_UNIT_WIDTH*i,GRID_X_BOTTOM);}Penmypen1=newPen(Color.Black,2);g.DrawLine(mypen1,GRID_X_LEFT-GRID_UNIT_WIDTH,GRID_X_TOP,GRID_X_LEFT-GRID_UNIT_WIDTH,GRID_X_BOTTOM);for(inti=0;i<Y_UNIT_NUM;i++){g.DrawLine(mypen,GRID_Y_LEFT,GRID_Y_TOP+GRID_UNIT_HEIGHT*i,GRID_Y_RIGHT,GRID_Y_TOP+GRID_UNIT_HEIGHT*i);}g.DrawLine(mypen1,GRID_Y_LEFT,GRID_Y_TOP+GRID_UNIT_HEIGHT*(Y_UNIT_NUM-1),GRID_Y_RIGHT,GRID_Y_TOP+GRID_UNIT_HEIGHT*(Y_UNIT_NUM-1));//x轴for(inti=0;i<WEEKDAYS;i++){g.DrawString(ds.Tables[0].Rows[i][0].ToString().Substring(ds.Tables[0].Rows[i][0].ToString().LastIndexOf(',')+1),font_Note,Brushes.Black,CHAR_X_LEFT+GRID_UNIT_WIDTH*i,CHAR_X_TOP);}//y轴string[]money={"5000CNY","4500CNY","4000CNY","3500CNY","3000CNY","2500CNY","2000CNY","1500CNY","1000CNY","500CNY"};for(inti=0;i<Y_UNIT_NUM;i++){g.DrawString(money[i].ToString(),font_Note,Brushes.Black,CHAR_Y_LEFT,CHAR_Y_TOP+DATA_UNIT_HEIGHT*i);}int[]Count=newint[WEEKDAYS];intj=0;for(j=0;j<WEEKDAYS;j++){Count[j]=Convert.ToInt32(ds.Tables[0].Rows[j][1].ToString());}//显示柱状效果ImageAttributesimgAtt=newImageAttributes();imgAtt.SetWrapMode(WrapMode.TileFlipXY);//为了使柱形图片无渐变色效果for(inti=0;i<WEEKDAYS;i++){g.DrawImage(bitmap,newRectangle(GRID_Y_LEFT+GRID_UNIT_WIDTH*i+(GRID_UNIT_WIDTH-DATA_UNIT_WIDTH)/2,(int)(380-(Count[i]>5000?5000:Count[i])*3F/50F),40,(int)(Count[i]*3F/50F)),0,0,bitmap.Width,bitmap.Height,GraphicsUnit.Pixel,imgAtt);}//创建其支持存储区为内存的流System.IO.MemoryStreamms=newSystem.IO.MemoryStream();//将此图像以指定格式保存到指定流中p_w_picpath.Save(ms,System.Drawing.Imaging.ImageFormat.Gif);//清除缓冲区流中的所有内容输出Response.ClearContent();//设置输出MIME类型Response.ContentType="p_w_picpath/Gif";Response.BinaryWrite(ms.ToArray());}else{intheight=380,width=570;System.Drawing.Bitmapp_w_picpath=newSystem.Drawing.Bitmap(width,height);Graphicsg=Graphics.FromImage(p_w_picpath);g.Clear(Color.White);//应用DrawString方法输出文字信息g.DrawString("无符合搜索条件数据",font_GraphicName,brush_Blue,newPointF(140,20));//创建其支持存储区为内存的流System.IO.MemoryStreamms=newSystem.IO.MemoryStream();//将此图像以指定格式保存到指定流中p_w_picpath.Save(ms,System.Drawing.Imaging.ImageFormat.Gif);//清除缓冲区流中的所有内容输出Response.ClearContent();//设置输出MIME类型Response.ContentType="p_w_picpath/Gif";Response.BinaryWrite(ms.ToArray());}}

可能大家看的比较晕,因为全是一些坐标的计算,这个大家就不用太多考虑,就知道图表和柱形的一个思路就行了。

最后总结一下,其实画图指定是离不开一些点在图中位移的计算,这里最好是自己能在草纸上比划一下,设计一下,这样会画的更快一些,同时也不容易被一大堆的数据搞晕。另外一点就是数据这块,柱形的比例搞好就行了,公式大概就是H(格子的实际高度)/h(单位刻度表示的价钱)=X(柱形的高度,我们所有求的)/price(当前柱的实际表示价钱,数据库中存的)。最后也就是X= H*price/h 这样。


想做出更加完美,智能的图表还有许多地方可以改进:

1、y轴展示的刻度我们可以动态变化,根据所查询出数据的最大值进行刻度计算,找出合适的单位刻度

2、x轴可以做一个额外的注释(当然,这里就是星期,也不太用到),但是如果是一些内容的话,可以根据坐标范围做鼠标移上有提示那种。

3、可以把柱形做的再美一点,更加立体一些,当然,这是需要依靠美工的力量。

Demo地址:http://down.51cto.com/data/1878284