终极 ASP.NET

使用 ASP.NET 和 LINQ 制图

K. Scott Allen

代码下载可从 MSDN 代码库
浏览代码联机

内容

快速入门
图表生成
发送数据
从仪表板的图表
图表未来

Microsoft 最近 ASP.NET 3.5 SP 1 的发布一个新的图表控件。图表控件易于使用,并允许您将吸引人的数据可视化效果添加到 ASP.NET Web 应用程序。该控件支持的所有标准图表类型的如线图和饼图,以及高级的可视化效果 (如漏斗和棱锥图。在本专栏,我将探讨图表控件,并生成使用查询对 LINQ 写入对象的某些数据。此列的完整源代码位于 MSDN 代码库。

fig01.gif

图 1 Visual Studio 工具

快速入门

第一步是下载图表控件从 Microsoft 下载中心。此下载将安装您需要的图表,包括全局程序集缓存 System.Web.DataVisualization 程序集的位置项运行时组件。

您还需要下载在Visual Studio 2008 工具支持图表示例 Web 站点. 工具支持将给出工具箱集成和示例网站将进行数百个示例,以检查到创建的需要的图表类型的灵感时支持设计时的 IntelliSense。请注意您将需要 Microsoft.NET Framework 3.5 运行库和 Visual Studio 2008 安装 Service Pack 1。

全部完成安装后,您能够在 Visual Studio 中创建一个新的 ASP.NET 项目并将在工具箱窗口中找到图表控件 (请参见 图 1 )。可以将图表拖到 ASPX 文件使用设计视图 (这将使到 Web.config 文件的一些必要的配置更改) 或直接在控件使用的 ASPX 的源视图中文件 (在这种情况下需要手动进行配置更改将立即描述)。

当您将图表放到 Web 窗体设计器中时,Visual Studio 会将新项到您的 web.config 文件的 <controls> 节像下面这样:

<add tagPrefix="asp" 
     namespace="System.Web.UI.DataVisualization.Charting"
     assembly="System.Web.DataVisualization, 
               Version=3.5.0.0, Culture=neutral, 
               PublicKeyToken=31bf3856ad364e35" />

此项允许您以其他内置的 ASP.NET 控件使用熟悉的 asp 标记前缀使用图表控件。 Visual Studio 还添加新 HTTP 处理程序条目到 IIS 7.0 <handlers> 部分和到 <httphandlers> 节 (用于与 IIS 6.0 和 Visual Studio Web Development Server) 一个类似的条目:

<add name="ChartImageHandler" 
     preCondition="integratedMode" 
     verb="GET,HEAD"
     path="ChartImg.axd"
     type="System.Web.UI.DataVisualization.Charting.Chart­           HttpHandler, 
           System.Web.DataVisualization, 
           Version=3.5.0.0, Culture=neutral,
           PublicKeyToken=31bf3856ad364e35" />

此 HTTP 处理程序负责处理请求到达 ChartImg.axd,这是默认终结点图表控件将用于提供图表。 我稍后将进入 HTTP 处理程序有关更多详细信息。 图 2 中的代码显示图表的基本元素。 每个图表包含用数据填充的至少一个系列对象。 每个系列的将 ChartType 属性将确定用于绘制数据系列 (默认类型是柱形图) 的图表的类型。 每个图表都可以包含在绘制将发生的一个或多个 ChartArea 对象。

图 2 A 基本图表

<asp:Chart ID="Chart1" runat="server" Height="300" Width="400">
    <Series>
        <asp:Series BorderColor="180, 26, 59, 105">
            <Points>
                <asp:DataPoint YValues="45" />
                <asp:DataPoint YValues="34" />
                <asp:DataPoint YValues="67" />
            </Points>
        </asp:Series>
    </Series>
    <ChartAreas>
        <asp:ChartArea />
    </ChartAreas>
</asp:Chart>

您可以自定义包括背景、 轴、 标题、 图例和标签在 ASP.NET 图表控件的几乎是每个 Visual 元素。 图表控件是因此高度可定制以将图表保存在应用程序查找一致应该有计划。 在本专栏,我将使用生成器策略将一致的字体和颜色应用于所有的图表。 该生成器类还将允许 ASP.NET 页的范围之外图表控件使用。

图表生成

在查找要在此列中使用的示例数据,我简要考虑使用为库存市场中的历史数据,但过去的一年中的该数据已被 depressing,因此而我决定使用交通统计 (bts.gov) 的美国部门中的数据。 为此列示例应用程序包括信息来自我的主页机场 (巴尔的摩华盛顿国际或 BWI) 在 2008 年 1 月每个国内飞行的文件。 数据包括所在的目标城市、 距离、 taxiing 时间和延迟。 在 C# 图 3 中使用类表示此数据。

图 3 Flight 数据

public class Flight {
    public DateTime Date { get; set; }
    public string Airline { get; set; }
    public string Origin { get; set; }
    public string Destination { get; set; }
    public int TaxiOut { get; set; }
    public int TaxiIn { get; set; }
    public bool Cancelled { get; set; }
    public int ArrivalDelay { get; set; }
    public int AirTime { get; set; }
    public int Distance { get; set; }
    public int CarrierDelay { get; set; }
    public int WeatherDelay { get; set; }
}

我此数据构建的第一个图表时显示最常用的目标,从巴尔的摩机场的航班的图表 (请参见 图 4 )。 此图表是使用非常少 ASPX 文件或其关联的源代码文件中的代码生成的。 ASPX 文件包含在页上的图表控件,但只设置宽度和高度属性中,正如您所看到的:

  <form id="form1" runat="server">
  <div>
    <asp:Chart runat="server" Width="800" Height="600" ID="_chart">
    </asp:Chart>
  </div>
</form>

fig04.gif

图 4 上的目标 (单击图像可查看大视图)

在代码隐藏委托 Page _ Load 事件处理一个 TopDestinationsChartBuilder 类的工作的所有如下所示:

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        var builder = new TopDestinationsChartBuilder(_chart);
        builder.BuildChart();
    }
}

在 TopDestinationsChartBuilder 继承 ChartBuilder 类。 此 ChartBuilder 文章类使用模板方法设计模式组合图表的各个部分。 该模板方法指定生产的 aesthetically 一致和功能图表所需的基本算法,但提供子类以自定义组装的片段的挂钩。 该模板方法称为 BuildChart,并且它显示在 图 5

图 5 BuildChart

public void BuildChart() {
    _chart.ChartAreas.Add(BuildChartArea());
    _chart.Titles.Add(BuildChartTitle());
    if (_numberOfSeries > 1) {
        _chart.Legends.Add(BuildChartLegend());
    }
    foreach (var series in BuildChartSeries()) {
        _chart.Series.Add(series);
    }
}

ChartBuilder 类中的每种生成方法都有关联的自定义方法。 一旦 Build 方法已构造的一部分图表,它将调用该自定义方法。 派生的类可以重写应用图表特定设置自定义方法。 一个类的示例是 图 6 中所示,BuildChartTitle 方法。 TopDestinationsChartBuilder 类重写应用标题的特定文本 CustomizeChartTitle:

protected override void  CustomizeChartTitle(Title title)
{
    title.Text = "Top Destinations";     
}

图 6 BuildChartTitle

private Title BuildChartTitle() {
    Title title = new Title() {
        Docking = Docking.Top,
        Font = new Font("Trebuchet MS", 18.0f, FontStyle.Bold),
    };
    CustomizeChartTitle(title);
    return title;
}

protected virtual void CustomizeChartTitle(Title title) { }

在 TopDestinationsChartBuilder 工作大部分专用于自定义图表系列包括添加的所有显示的数据点。 幸运的是,查找顶部的五个目标城市,Flight 对象的集合中就 ridiculously 简单对象,使用 LINQ 的可以看到在 图 7 中。 代码首先应用到组按其目标城市航班的 LINQ 的 GroupBy 运算符。 OrderByDescending 运算符然后排列航班的数,每个组中的分组的顺序。 最后,代码使用采用运算符确定从规则上的五个目标。

图 7 获取顶部五城市

protected override void CustomizeChartSeries(IList<Series> seriesList) {
    var repository = FlightRepositoryFactory.CreateRepository();
    var query = repository.Flights
                          .GroupBy(flight => flight.Destination)
                          .OrderByDescending(group => group.Count())
                          .Take(5);
    Series cities = seriesList.Single();
    cities.Name = "Cities"; 
    foreach (var record in query) {
        cities.Points.AddXY(record.Key, record.Count());
    }
}

LINQ 查询将产生一系列分组。 您可以循环将信息添加到图表的这些分组。 每个分组的关键属性代表 GroupBy 运算符 (在本例中的目标城市值) 中选中项的值。 代码将作为 X 值对于每个数据点的目标。 每个分组还包含其分组在此目标 Flight 对象的可枚举序列。 只需使用计数运算符来获取每个目标的航班的总数并添加为每个数据点的 Y 值的计数。

发送数据

而不是一次,将数据添加到图表系列的一个点的可以在图表的 DataPointCollection 上使用一个 DataBindXY 方法,要在不使用循环的情况下中插入一系列数据点。 可以看到这一点在 DelaysByDayChartBuilder (以分钟为单位) 计算所有延迟的总计的中的月份中每一天。 该生成器还生成一个第二列显示总的天气相关延迟的数据。 生成器开头按月的第天分组在航班的 LINQ 查询。 项的属性,对每个分组现在 (从 1 到 31 一月份) 表示月的第天:

var query = repository.Flights
                              .GroupBy(flight => flight.Date.Day)
                              .OrderBy(group => group.Key)
                              .ToList();

图 8 中的代码使用 DataBindXY 方法。 首先,所有 X 值都收集到使用选择的运算符获取每个组的键值的列表。 接下来,其他处理适用于初始的分组查询内的每个组,ArrivalDelay 和 WeatherDelay 值求和。

图 8 插入值不循环

var xValues = query.Select(group => group.Key).ToList();

totalDelaySeries.Points.DataBindXY( 
        xValues,
        query.Select(
            group => group.Sum(
                flight => flight.ArrivalDelay)).ToList());

weatherDelaySeries.Points.DataBindXY(
        xValues,
        query.Select(
            group => group.Sum(
                flight => flight.WeatherDelay)).ToList());

正如您所看到则标准的 LINQ 运算符使其易于进行切片和操作的报告和图表数据。 正在将 TaxiTimeChartBuilder 另一个很好的示例。 此类程序集雷达图显示一周中的每天总"taxi 出"时间。 在"taxi 出"时是时间的的飞机上花费将门和提升的机场 runway 之间。 在拥塞机场"taxi 出"时可以 soar 如最 planes 队列,并等待清除 runway。 在 TaxiTimeChartBuilder 突出显示数据点,其中"taxi 出"时间超过某个阈值的值。 此作业由 trivially 轻松使用系列中的 LINQ 查询对数据点:

var overThresholdPoints = 
    taxiOutSeries.Points
                 .Where(p => p.YValues.First() > _taxiThreshold);
foreach (var point in overThresholdPoints)
{
    point.Color = Color.Red;
}

此处的每个数据点超过指定的阈值的颜色更改为红色。此问题可能会使您考虑一个机场) 仪表板报告因此,让我们看仪表板下一步。

从仪表板的图表

在商务智能圆圈中用于仪表板以图形方式显示有关业务关键的性能信息。此性能信息可以来自各种源,并可以使用一系列的图表和小部件将可视化。用户应该能够在仪表板显示 glance,并快速确定可能影响业务的任何异常。

在生成仪表板显示挑战之一是性能。组合的所有仪表板的信息可能导致的查询的许多关系和多维数据库。即使在仪表板的原始数据缓存,纯粹数图表和仪表板上的小部件可以 burden 服务器。

图 9 显示为一周中的一周,每一天总 flights 和每天月份的总延迟时间中的某一天显示最多目标,taxi 时间机场简单仪表板。生成此仪表板的一个方法是将单个的网页上的所有四个图表控件,然后使用图表生成器类,我们已经被构建为组合图表。但是,假设每个图表是否需要生成的两秒。通常,两秒不是长时间等待一个复杂的查询,但因为我有个页 (这是在仪表板的少量) 上的四个图表,将用户等待至少八秒钟为第一个图形显示。

fig09.gif

图 9 机场活动仪表板

生成此仪表板页面一个不同的方法是定义页中的每个图表的占位符,并异步生成图表图像。异步方法将允许用户以查看第一个结果,为可用。此解决方案可以利用 Windows Communication Foundation (WCF) 代理变为可用时显示图表的 JavaScript 中。

幸运的是,我已定义该图表的生成器类使容易移动图表生成逻辑,WCF Web 服务后。图 10 中的代码演示一个 WCF 服务方法,第一次构造一个空的图表和然后构造 ChartBuilder 派生类在项目中实现之一。生成器指定为该的方法的一个参数,且该代码检查此参数,对的已知生成器来查找要实例化 (使用 Activator.CreateInstance) 的 Builder 的实际类型。

图 10 实例化生成器

[OperationContract]
public string GenerateChart(string builderName) {
    Chart chart = new Chart() {
        Width = 500, Height = 300
    };

    ChartBuilder builder = 
        Activator.CreateInstance(_typeMap[builderName], chart)
            as ChartBuilder;
    builder.BuildChart();

    return SaveChart(builderName, chart);
}

使用的已知的生成器类型意味着,我们不必向方法传递一个的实际类型名称,它还提供对从 Internet 上的恶意输入的保温的层。 我们将只实例化服务知道的类型。

生成 Web 服务中的图表图像是棘手。 如我们前面提到,图表使用 HTTP 处理程序以呈现在客户端上的图表。 特别是,图表控件提供 ChartHttpHandler 类字节表示图表的完成的图像。 在 ChartHttpHandler 响应具有唯一的标识符。 图表控件呈现时, 它生成普通的 HTML <img> 标记 src 属性引用 ChartImg.axd,并包括在查询字符串中的唯一标识符。 当此图像请求到达 HTTP 处理程序时,该处理程序可以查找显示正确的图表。 您可以阅读有关这一过程包括上的所有配置选项的详细信息 delian Tchoparino 博客.

遗憾的是,HTTP 处理程序,API 不是公共,并且因此不可 Web 服务中。 相反,则 SaveChart 为方法这将调用该服务代码 图 10 中的使用图表控件的 SaveImage 方法写入文件系统以 PNG 格式的图表图像。 该服务然后返回到客户端的文件的名称。 通过生成物理文件我们还可以引入一个缓存策略,并避免查询和较重负载时的图像生成。

图 11 中的代码使用 WCF 服务从 JavaScript 设置每个图表图像占位符的 src 属性。 (请参见 Fritz Onion 的列" AJAX Extensions 客户端 Web 服务调用"要理解如何生成 JavaScript 代理,并调用 Web 服务使用 JavaScript)。 文档对象模型 (DOM) 的标识符和生成器类的一个 JavaScript 数组,它是"上下文"对象的一部分中定义每个图表。 在此上下文被隧道通过连续 Web 服务调用 userState 参数中 Web 服务代理方法。 JavaScript 使用上下文对象来跟踪其进度更新仪表板图。 动态的仪表板页面,服务器可以动态生成数组。

图 11 更新该图表

/// <reference name="MicrosoftAjax.js" />
function pageLoad() {
    var context = {
        index: 0,
        client: new ChartingService(),
        charts: 
        [
            { id: "topDestinations", builder: "TopDestinations" },
            { id: "taxiTime", builder: "TaxiTime" },
            { id: "dayOfWeek", builder: "DayOfWeek" },
            { id: "delaysByDay", builder: "DelaysByDay" }
        ]
    };

    context.client.GenerateChart(
        context.charts[context.index].builder,
        updateChart,
        displayError,
        context);
}

function updateChart(result, context) {
    var img = $get(context.charts[context.index].id);
    img.src = result;
    context.index++;
    if (context.index < context.charts.length) {
        context.client.GenerateChart(
            context.charts[context.index].builder,
            updateChart, displayError, context);
    }
}

function displayError() {
    alert("There was an error creating the dashboard charts");
}

图表未来

我已经介绍了包括模板方法设计模式、 LINQ 运算符和 JavaScript 兼容 WCF Web 服务技术,相当一段。 因为此列说明只小小部分图表控件中可用该功能,务必检查示例 Web 站点以查看先进广泛的功能。 与灵活性和 LINQ 的表示组合此强大的可视化工具,则意味着将来图表应用程序将更灵活、 有效,且有用。

K Scott Allen 是 Pluralsight 技术人员的成员和 OdeToCode 的创始人。 您可以访问在 Scott Scott@OdeToCode.com 或读取在他的博客 odetocode.com/blogs/Scott.