本文共 7776 字,大约阅读时间需要 25 分钟。
对于一些内部系统的项目,各种图表是在所难免的,因为图表可以更加清晰的表达出想看到的数据。
因为之前从来没有做过关于图表的东西,唯一能想到的就是“验证码”,所以应该是一个思路,用GDI去搞。
数据懒着去搞了,记得前些日子在亚航官网查机票,就想到这些数据还挺适合做这个DEMO的,所以就先借用一下亚航的数据喽。
数据大概就是这样子的,日期及价钱。
我选了其中“9月27日-10月10日”正好两周的数据作为此次Demo的测试数据。
原理就是跟实现验证码一模一样,通过给<img>标签修改src属性来操作柱形图的变化,src属性导向的页面是另外一个页,非显示柱形图本页。
然后就是如何利用GDI画柱形图。
先上一下效果图:
下面看一下部分代码片段
html代码:
1 2 3 4 5 6 7 8 9 10 11 | < body > < form id = "form1" runat = "server" > < div > < asp:ImageButton ID = "img_last" runat = "server" ImageUrl = "~/Left.jpg" AlternateText = "Left" OnClientClick = "return false;" /> < img id = "img_bar" src = "GenerateImage.aspx?page_num=0&from=北京&to=清迈" alt = "" /> < asp:ImageButton ID = "img_next" runat = "server" ImageUrl = "~/Right.jpg" AlternateText = "Right" OnClientClick = "return false;" /> </ div > < asp:HiddenField ID = "hf_pagenum" runat = "server" Value = "0" /> < asp:HiddenField ID = "hf_recordnum" runat = "server" /> </ form > </ body > |
js代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | $( 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、一些坐标点的声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | private readonly int WEEKDAYS = 7; //一周的天数 private readonly int Y_UNIT_NUM = 10; private readonly int CHAR_X_LEFT = 110; private readonly int CHAR_X_TOP = 388; private readonly int CHAR_Y_LEFT = 30; private readonly int CHAR_Y_TOP = 79; private readonly int TITLE_LEFT = 140; //柱形图标题 起始X坐标 private readonly int TITLE_TOP = 18; //柱形图标题 起始Y坐标 private readonly int TIME_LEFT = 170; //柱形图日期 起始X坐标 private readonly int TIME_TOP = 48; //柱形图日期 起始Y坐标 private readonly int GRID_X_LEFT = 150; //背景网格 X左边位移 private readonly int GRID_X_TOP = 80; //背景网格 X上边位移 private readonly int GRID_X_BOTTOM = 380; //背景网格 X下边位移 private readonly int GRID_Y_TOP = 110; private readonly int GRID_Y_LEFT = 100; private readonly int GRID_Y_RIGHT = 580; private readonly int GRID_UNIT_WIDTH = 50; //网格单元宽度 private readonly int GRID_UNIT_HEIGHT = 30; //网格单元高度 private readonly int DATA_UNIT_WIDTH = 40; //柱单元宽度 private readonly int DATA_UNIT_HEIGHT = 30; //柱单元高度 |
2、筛选当页数据方法
为了是通过点击下一页,上一页按钮,对数据进行筛选,选出当页显示数据进行绘制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | protected DataSet QueryDisplayRecord( int pagenum) { DataSet ds = new DataSet(); DataTable dt = new DataTable(); dt.Columns.Add( new DataColumn( "date" )); dt.Columns.Add( new DataColumn( "price" )); for ( int i = 7 * pagenum; i < (goStr.Count >= WEEKDAYS * (pagenum + 1) ? WEEKDAYS * (pagenum + 1) : goStr.Count); i++) { DataRow dr = 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); return ds; } |
哦,对了,这里用的是dictionary<string, string>暂时存的模拟数据,正常情况当然要是通过数据库了。
3、生成图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | protected void CreateImage( string from , string to, int pagenum, DataSet ds) { Font font_Note = new System.Drawing.Font( "Arial" , 9, FontStyle.Regular); //x轴,y轴,解释内容字体 小字 Font font_GraphicName = new System.Drawing.Font( "微软雅黑" , 18, FontStyle.Regular); //图表名称 字体 大字 Font font_GraphicTime = new System.Drawing.Font( "微软雅黑" , 14, FontStyle.Regular); //图表时间 字体 大字 Brush brush_Blue = new SolidBrush(Color.Black); //蓝色线条 if (ds != null ) { int height = 620, width = 650; System.Drawing.Bitmap image = new System.Drawing.Bitmap(width, height); Graphics g = Graphics.FromImage(image); g.Clear(Color.White); System.Drawing.Drawing2D.LinearGradientBrush brush = new System.Drawing.Drawing2D.LinearGradientBrush( new Rectangle(0, 0, image.Width, image.Height), Color.LightGray, Color.LightGray, 1.2f, true ); //调用FillRectangle方法使用指定颜色填充柱形图的内部 g.FillRectangle(Brushes.WhiteSmoke, 0, 0, width, height); Brush brush1 = new SolidBrush(Color.Black); //应用DrawString方法输出文字信息 g.DrawString( from + " 至 " + to + " 机票分析图 (单位:CNY)" , font_GraphicName, brush_Blue, new PointF(TITLE_LEFT, TITLE_TOP)); //画图片的边框线 g.DrawRectangle( new Pen(Color.Black), 0, 0, image.Width - 1, image.Height - 1); Pen mypen = new Pen(brush, 1); //绘制横向线条 for ( int i = 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); } Pen mypen1 = new Pen(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 ( int i = 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 ( int i = 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 ( int i = 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 = new int [WEEKDAYS]; int j = 0; for (j = 0; j < WEEKDAYS; j++) { Count[j] = Convert.ToInt32(ds.Tables[0].Rows[j][1].ToString()); } //显示柱状效果 ImageAttributes imgAtt = new ImageAttributes(); imgAtt.SetWrapMode(WrapMode.TileFlipXY); //为了使柱形图片无渐变色效果 for ( int i = 0; i < WEEKDAYS; i++) { g.DrawImage(bitmap, new Rectangle(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.MemoryStream ms = new System.IO.MemoryStream(); //将此图像以指定格式保存到指定流中 image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); //清除缓冲区流中的所有内容输出 Response.ClearContent(); //设置输出MIME类型 Response.ContentType = "image/Gif" ; Response.BinaryWrite(ms.ToArray()); } else { int height = 380, width = 570; System.Drawing.Bitmap image = new System.Drawing.Bitmap(width, height); Graphics g = Graphics.FromImage(image); g.Clear(Color.White); //应用DrawString方法输出文字信息 g.DrawString( "无符合搜索条件数据" , font_GraphicName, brush_Blue, new PointF(140, 20)); //创建其支持存储区为内存的流 System.IO.MemoryStream ms = new System.IO.MemoryStream(); //将此图像以指定格式保存到指定流中 image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); //清除缓冲区流中的所有内容输出 Response.ClearContent(); //设置输出MIME类型 Response.ContentType = "image/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