对于一些内部系统的项目,各种图表是在所难免的,因为图表可以更加清晰的表达出想看到的数据。
因为之前从来没有做过关于图表的东西,唯一能想到的就是“验证码”,所以应该是一个思路,用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