图形、GDI + 和图表(Chart 控件)
创建图表是最常见的图形任务。ASP.NET Chat 控件提供了众多的图表类型和配置选项。对于 ASP.NET 3.5 SP1,Chart 控件可以下载使用,4.0 中已经包括了。
Chart 控件有众多不同特性及选项,这里不可能全面讲述。如果需要一整套示例,可参考 http://code.msdn.microsoft.com/mschart 的示例代码库,它包含了 200 多个 Chart 控件示例。
创建基本的图表
理解 Chart 最好的方式还是通过示例。首先,拖出 Chart 控件:
<asp:Chart ID="Chart1" runat="server" Width="900px">
<ChartAreas>
<asp:ChartArea Name="ChartArea1" />
</ChartAreas>
</asp:Chart>
每个图表有一个或多个描绘数据的图形区域。我们的基本声明里有一个图表,我们将用它描绘两组数据。和多数 ASP.NET 控件一样,可以使用隐藏文件来驱动 Chart 控件或继续使用标记。
1. 使用代码隐藏方式
首先配置图表的外观。图表背景是包围图表区域的区域。本例以对角线模式从灰到白渐变填充背景色,通过 BackColor、BackSecondaryColor、BackGradientStyle 属性指定。
protected void Page_Load(object sender, EventArgs e)
{
// format the chart.
Chart1.BackColor = Color.Gray;
Chart1.BackSecondaryColor = Color.WhiteSmoke;
Chart1.BackGradientStyle = GradientStyle.DiagonalRight;
...
BorderlineDashStyle 控制每个边框的边缘;ChartDashStyle.Solid 值在图表的边缘绘制一个像素宽的边线;BorderlineColor 决定边线的颜色;BorderSkin 有多个选项,把 SkinStyle 设置为 Emboss 显示有阴影的凸起的 3D 效果。
...
Chart1.BorderlineDashStyle = ChartDashStyle.Solid;
Chart1.BorderlineColor = Color.Gray;
Chart1.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
...
把图形区域设置成我们想要的颜色并设置标题。通过一个索引值来引用图表区域,这是因为图表可以有多个描绘数据的区域(我们的图表目前只有一个区域)。图表可以有多个标题,因此必须通过 Add()方法把新标题添加到 Titles 集合,然后通过索引找到对应标题进行格式化。这里只设置了字体和大小。
// forma the chart area
Chart1.ChartAreas[0].BackColor = Color.Wheat;
// add and format the title
Chart1.Titles.Add("ASP.NET Chart");
Chart1.Titles[0].Font = new Font("Utopia", 16);
有多种格式化图表的方法,找到你期望的组合会花费不少的时间。具有讽刺意味的是,添加数据的过程要比找到满意的颜色方案更加简单。目前的效果是这样的:
有了基础的表现设置后,可以开始处理数据了。通过 Series 类来表示成组的数据。我们创建的每个 Series 类都必须通过 Add 方法被添加到 Chart.Series 集合。我们创建一个名为 ColumnSeries 的新序列,并指定用柱状图来描绘这个序列里的数据。SeriesChartType 有 35 个不同的图表类型值,它们值得探索。如果不指定值,默认就是 Column 。
Chart1.Series.Add(new Series("ColumnSeries")
{
ChartType = SeriesChartType.Column
});
序列有一系列属性可以进行配置。这是我们为图表创建第二个 Series 的代码,其中进行了自定义:
Chart1.Series.Add(new Series("SplineSeries")
{
ChartType = SeriesChartType.Spline,
BorderWidth = 3,
ShadowOffset = 2,
Color = Color.PaleVioletRed
});
这里把图表类型设置为 Spline(折线图)。BorderWidth 的值控制线描绘时的宽度,ShadowOffset 设置了在描绘线的时候创建 3D 的阴影效果。
最后一步是向序列添加数据。有很多方式可以把数据关联到图表,这个示例使用最简单的静态值:
Chart1.Series[0].Points.DataBindY(
new int[] { 5, 3, 12, 14, 11, 7, 3, 5, 9, 12, 11, 10 });
Chart1.Series[1].Points.DataBindY(
new int[] { 3, 7, 13, 2, 7, 15, 23, 20, 1, 5, 7, 6 });
每个 Series 都有一个 Point 集合。我们使用 DataBindY 方法,它接受一个 IEnumerable 对象,可以把整型数组作为数据源。这些数据的索引被用作 X 轴的值,而数据值被用在 Y 轴。
现在运行页面,可以看到这样的效果:
2. 使用页面标记配置
<asp:Chart ID="Chart2" runat="server" Width="900px" BackColor="Gray" BackSecondaryColor="WhiteSmoke"
BackGradientStyle="DiagonalRight" BorderlineDashStyle="Solid" BorderlineColor="Gray">
<BorderSkin SkinStyle="Emboss" />
<Titles>
<asp:Title Text="ASP.NET Chart" Font="Utopia,16">
</asp:Title>
</Titles>
<Series>
<asp:Series Name="ColumnSeries" ChartType="Column">
<Points>
<asp:DataPoint YValues="5" />
<asp:DataPoint YValues="3" />
<asp:DataPoint YValues="12" />
<asp:DataPoint YValues="14" />
<asp:DataPoint YValues="11" />
<asp:DataPoint YValues="7" />
<asp:DataPoint YValues="3" />
<asp:DataPoint YValues="5" />
<asp:DataPoint YValues="9" />
<asp:DataPoint YValues="12" />
<asp:DataPoint YValues="11" />
<asp:DataPoint YValues="10" />
</Points>
</asp:Series>
<asp:Series Name="SplineSeries" ChartType="Spline" BorderWidth="3" ShadowOffset="2"
Color="PaleVioletRed">
<Points>
<asp:DataPoint YValues="3" />
<asp:DataPoint YValues="7" />
<asp:DataPoint YValues="13" />
<asp:DataPoint YValues="2" />
<asp:DataPoint YValues="7" />
<asp:DataPoint YValues="15" />
<asp:DataPoint YValues="23" />
<asp:DataPoint YValues="20" />
<asp:DataPoint YValues="1" />
<asp:DataPoint YValues="5" />
<asp:DataPoint YValues="7" />
<asp:DataPoint YValues="6" />
</Points>
</asp:Series>
</Series>
<ChartAreas>
<asp:ChartArea Name="ChartArea1" BackColor="Wheat">
</asp:ChartArea>
</ChartAreas>
</asp:Chart>
Chart 控件的灵活性在于各种元素间相互独立。为了证明这一点,下一个示例演示如何在同一个图表上创建第二个图标区域,并且让同一个数据序列分别在两个区域上描绘,在第一个示例的 Load 事件里增加下面的代码:
Chart1.ChartAreas.Add("SecondArea");
Chart1.Series[1].ChartArea = "SecondArea";
如果使用标记要达到同样的效果,只需指定折线图的 ChartArea 特性即可:
<asp:Series Name="SplineSeries" ChartType="Spline" BorderWidth="3" ShadowOffset="2"
Color="PaleVioletRed" ChartArea="SecondArea">
有一个常见的任务是创建 3D 图表。为了把一个简单的图表转换为 3D 图表,需要添加下列代码:
Chart1.ChartAreas[0].Area3DStyle.Enable3D = true;
在 ChartArea 级别启用 3D 。如果图表有多个 ChartArea,可以按照需求混合 2D 和 3D 描绘。
用数据填充图表
Chart 控件可以通过一系列不同数据源的数据来填充。
1. 绑定到数据库表
使用 Northwind 数据库中的 Products 表来演示。查询 ProductName 和 UnitsInStock 列,选择没有停产的产品并且只返回至多 5 条记录。
protected void Page_Load(object sender, EventArgs e)
{
Chart1.BackColor = Color.Gray;
Chart1.BackSecondaryColor = Color.WhiteSmoke;
Chart1.BackGradientStyle = GradientStyle.DiagonalRight;
Chart1.BorderlineDashStyle = ChartDashStyle.Solid;
Chart1.BorderlineColor = Color.Gray;
Chart1.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
Chart1.ChartAreas[0].BackColor = Color.Wheat;
Chart1.Titles.Add("Table Bound Chart");
Chart1.Titles[0].Font = new Font("Utopia", 16);
// Data Binding ...
SqlConnection conn = new SqlConnection(
ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString);
SqlCommand cmd = new SqlCommand("select top (5) ProductName, UnitsInStock " +
"from Products where Discontinued = 'false'", conn);
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
...
现在该把数据绑定到图表了。首先,清空 Chart.Series 集合。这是因为绑定到表时,Chart 控件自动为它在数据中找到的数值列创建一个新的 Series 实例,最终我们会得到一个名为 UnitsInStock 的 Series,但默认的 Series 并没有被移除(名为 Series1),如果把 Series.ChartType 属性改为不可以作为柱状图在同一 CharArea 描绘的其他类型的值就会产生问题,而柱状图是默认的序列图表类型。
在绑定前移除默认的 Series 类是好的实践,这样最终只有一个 Series 类关联到我们的数据!
Chart1.Series.Clear();
Chart1.DataBindTable(reader);
Chart1.Series["UnitsInStock"].ChartType = SeriesChartType.StackedBar;
reader.Close();
conn.Close();
绑定到表时会自动根据数据中的每个数值列创建相同数量的数据序列。示例里,Chart 控件忽略了文本类型的 ProductName 列,而为 UnitsInStock 列创建序列。
只有 Series 创建后才能对其进行格式化,因此只有把表绑定到控件之后,才能设置 ChartType 属性。
运行效果如下:
这个图表的问题是 ProductName 列被忽略了。在绑定过程中,Chart 控件按所有 SQL 数据都被解析为 Y 轴的数据的机制工作,从而放弃了不可以这样使用的其他所有值。
如果我们希望把某列用作 Y 轴的值,就必须通过 Series 类的 Points.DataBindXY 方法绑定数据来显式的通知 Chart 控件。可将先前的代码修改为如下:
//Chart1.Series.Clear();
//Chart1.DataBindTable(reader);
//Chart1.Series["UnitsInStock"].ChartType = SeriesChartType.StackedBar;
Chart1.Series[0].Points.DataBindXY(reader, "ProductName", reader, "UnitsInStock");
Chart1.Series[0].ChartType = SeriesChartType.StackedBar;
DataBindXY 方法可以指定 X轴 和 Y轴 的数据源以及名称。注意,这一次没有清空 Series 集合。DataBindXY 方法被应用到已经存在的 Series(并且不会创建新的)。
2. 绑定到对象数据源
填充图表数据最灵活的方式是使用对象数据源,这样可以创建一个获取数据的对象,并把 ObjectDataSource 用作代码检索逻辑和 Chart 控件之间的桥梁。应该在 App_Code 目录里创建获取数据的类。
下面是示例,它返回简单的静态数据:
public class MyObjectDataSource
{
public class DataItem
{
public string Name { get; set; }
public double Popularity { get; set; }
}
public DataItem[] GetData()
{
return new DataItem[]{
new DataItem(){Name = "CheeseCake", Popularity = 30},
new DataItem(){Name = "Ice Cream", Popularity = 30},
new DataItem(){Name = "Fudge", Popularity = 20},
new DataItem(){Name = "Milkshake", Popularity = 20}
};
}
}
这项绑定技术对于返回 DataSet 和 DataTable 的方法同样适用。使用 ObjectDataSource 的灵活性在于数据存储的方式对图表是抽象的,这样你可以自由修改业务逻辑。
protected void Page_Load(object sender, EventArgs e)
{
Chart1.BackColor = Color.Gray;
Chart1.BackSecondaryColor = Color.WhiteSmoke;
Chart1.BackGradientStyle = GradientStyle.DiagonalRight;
Chart1.BorderlineDashStyle = ChartDashStyle.Solid;
Chart1.BorderlineColor = Color.Gray;
Chart1.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
Chart1.ChartAreas[0].BackColor = Color.Wheat;
Chart1.ChartAreas[0].Area3DStyle.Enable3D = true;
Chart1.Titles.Add("Table Object Adaptor Chart");
Chart1.Titles[0].Font = new Font("Utopia", 16);
// Create the object data source
ObjectDataSource ds = new ObjectDataSource("MyObjectDataSource", "GetData");
// bind the source to the chart
Chart1.DataSource = ds;
Chart1.Series[0].XValueMember = "Name";
Chart1.Series[0].YValueMembers = "Popularity";
// format the series
Chart1.Series[0].ChartType = SeriesChartType.Pie;
}
效果如下:
3. 绑定到 XML 文件
你还可以通过 System.Data.DataSet 类用 XML 文件的数据填充表。
<?xml version="1.0" encoding="utf-8" ?>
<Data>
<Product>
<Name>Apple</Name>
<Quantity>40</Quantity>
</Product>
<Product>
<Name>Orange</Name>
<Quantity>20</Quantity>
</Product>
<Product>
<Name>Banana</Name>
<Quantity>30</Quantity>
</Product>
<Product>
<Name>Mango</Name>
<Quantity>22</Quantity>
</Product>
<Product>
<Name>Cherry</Name>
<Quantity>38</Quantity>
</Product>
</Data>
protected void Page_Load(object sender, EventArgs e)
{
Chart1.BackColor = Color.Gray;
Chart1.BackSecondaryColor = Color.WhiteSmoke;
Chart1.BackGradientStyle = GradientStyle.DiagonalRight;
Chart1.BorderlineDashStyle = ChartDashStyle.Solid;
Chart1.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
Chart1.BorderlineColor = Color.Gray;
Chart1.ChartAreas[0].BackColor = Color.Wheat;
Chart1.ChartAreas[0].Area3DStyle.Enable3D = true;
Chart1.Titles.Add("XML Chart");
Chart1.Titles[0].Font = new Font("Utopia", 16);
// 雷达图形
Chart1.Series[0].ChartType = SeriesChartType.Radar;
// define the path to the xml file
string dataPath = MapPath(".") + "\\sampledata.xml";
DataSet dataSet = new DataSet();
dataSet.ReadXml(dataPath);
DataView dataView = new DataView(dataSet.Tables[0]);
// bind the XML ata to the chart
Chart1.Series[0].Points.DataBindXY(dataView, "Name", dataView, "Quantity");
}
4. 绑定到 LINQ
将数据绑定到图表的另一种灵活的方式是使用 LINQ 。而且,不论你使用的是 LINQ to Objects、LINQ to XML 还是 LINQ to Entities,你都可以这么做。
首先,为 Northwind 数据库创建实体数据模型,接着,创建一个包含 Chart 控件的页面进行测试:
protected void Page_Load(object sender, EventArgs e)
{
Chart1.BackColor = Color.Gray;
Chart1.BackSecondaryColor = Color.WhiteSmoke;
Chart1.BackGradientStyle = GradientStyle.DiagonalRight;
Chart1.BorderlineDashStyle = ChartDashStyle.Solid;
Chart1.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
Chart1.BorderlineColor = Color.Gray;
Chart1.ChartAreas[0].BackColor = Color.Wheat;
Chart1.Titles.Add("LINQ Chart");
Chart1.Titles[0].Font = new Font("Utopia", 16);
// add and format a new data series
Chart1.Series.Add("StockLevel");
Chart1.Series["StockLevel"].ChartType = SeriesChartType.Spline;
Chart1.Series["StockLevel"].BorderWidth = 3;
Chart1.Series["StockLevel"].Color = Color.PaleVioletRed;
// create a new EF context
NorthwindEntities context = new NorthwindEntities();
// perform a query
var data = context.Products
.Where(item => !item.Discontinued)
.Select(item => item)
.Take(5);
// bind the default data series to the data
Chart1.Series[0].Points.DataBind(data, "ProductName", "UnitPrice", "");
Chart1.Series[1].Points.DataBind(data, "ProductName", "UnitsInStock", "");
}
创建了 NorthwindEntities 类的一个新实例,使用 LINQ 仍旧查询未停产的所有产品,将结果限制为 5 条。
紧接着通过 DataBind 方法将查询结果绑定至图表中的两个 Series。这将指定用于 X轴 和 Y轴 的数据源和结果类型成员。我们把 UnitPrice 值绑定到一个 Series 并把 UnitsInStock 绑定到另一个,它们都将 ProductName 的值用作 X轴 的值。
Chart 控件是一个强大而灵活的图表工具,它提供多种类型的图表格式并且可以使用来自各种数据源的数据,只需要很少的努力,就可以创建高质量的结果。