博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ASP.NET 简单的柱形图实现(附带示例)
阅读量:6655 次
发布时间:2019-06-25

本文共 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

本文转自 我不会抽烟 51CTO博客,原文链接:http://blog.51cto.com/zhouhongyu1989/1559413,如需转载请自行联系原作者
你可能感兴趣的文章